Code files of C++ nodes¶
C++ nodes are shared object files and must end on .so
.
Node class¶
The node is a C++ class derived from Flows::INode
of the library libhomegear-node
.
In addition a factory class derived from Flows::NodeFactory
and a function getFactory
are required.
getFactory
must return an instance to the factory class. The factory object must return a pointer to an instance of the node class.
This is required for Node-BLUE to be able to dynamically load the binary node file. As the factory part is the same for all nodes, you can just copy and paste it without having to worry about the inner workings.
MyNode.h¶
#ifndef MY_NODE_H_
#define MY_NODE_H_
#include <homegear-node/NodeFactory.h>
#include <homegear-node/INode.h>
class MyFactory : Flows::NodeFactory {
public:
Flows::INode *createNode(const std::string &path, const std::string &type, const std::atomic_bool *frontendConnected) override;
};
extern "C" Flows::NodeFactory *getFactory();
namespace MyNode {
class MyNode : public Flows::INode {
public:
MyNode(const std::string &path, const std::string &type, const std::atomic_bool *frontendConnected);
~MyNode() override;
};
}
#endif
MyNode.cpp¶
#include "MyNode.h"
Flows::INode *MyFactory::createNode(const std::string &path, const std::string &type, const std::atomic_bool *frontendConnected) {
return new MyNode::MyNode(path, type, frontendConnected);
}
Flows::NodeFactory *getFactory() {
return (Flows::NodeFactory *)(new MyFactory);
}
namespace MyNode {
MyNode::MyNode(const std::string &path, const std::string &type, const std::atomic_bool *frontendConnected) : Flows::INode(path, type, frontendConnected) {
}
MyNode::~MyNode() = default;
}
Receiving messages¶
To receive messages, you need to implement the method input
. This method is executed whenever a new message arrives.
MyNode.h¶
void input(const Flows::PNodeInfo &info, uint32_t inputIndex, const Flows::PVariable &message) override;
MyNode.cpp¶
void MyNode::input(const Flows::PNodeInfo &info, uint32_t inputIndex, const Flows::PVariable &message) {
try {
// Do something with 'message'
}
catch (const std::exception &ex) {
_out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what());
}
}
Multiple inputs¶
If the node has more than one input, the input the message arrived at can be read from inputIndex
.
Handling errors and logging events¶
Flows::INode
provides the _out
object for logging messages. It provides the following methods. All methods have one parameter: The message string.
Method | Description |
---|---|
printEx |
Use this method to log exceptions with file name, line number and function name. |
printCritical |
Critical errors (log level 1) |
printError |
Errors (log level 2) |
printWarning |
Warnings (log level 3) |
printInfo |
Info (log level 4) |
printDebug |
Debug (log level 5) |
printMessage |
Takes two parameters: Message and log level of the message. |
The log message is written to Homegear's flows log file. Warnings and errors also trigger Catch
nodes and - if not handled by a Catch
node - are written to debug tabs of connected frontends.
Frontent log¶
You can also log messages to the editor's event log only by calling frontendEventLog(const std::string &message)
.
frontendEventLog("My log message");
Calling Homegear RPC methods¶
Homegear RPC methods can be called with invoke
:
auto parameters = std::make_shared<Flows::Array>();
parameters->reserve(2);
parameters->emplace_back(std::make_shared<Flows::Variable>("TEST"));
parameters->emplace_back(std::make_shared<Flows::Variable>("value"));
auto result = invoke("setSystemVariable", parameters);
For an overview of the available RPC methods, visit Homegear's RPC reference.
Sending messages¶
To send a message, just call the method output
from any method of your class:
Flows::PVariable message = std::make_shared<Flows::Variable>(Flows::VariableType::tStruct);
message->structValue->emplace("payload", std::make_shared<Flows::Variable>("Using C++ to create nodes is even more awesome."));
output(0, message);
The first parameter is the index of the output, the second parameter is the message to send. Note that message
must be a Struct
and requires at least the entry payload
.
If the node is sending a message in response to having received one, it should reuse the received message rather than create a new message object. This ensures existing properties on the message are preserved for the rest of the flow.
Synchronous sending¶
By default messages are sent asynchronous. If you want to send them synchronous, set the third parameter to output
to true
:
//Synchronous sending
Flows::PVariable message = std::make_shared<Flows::Variable>(Flows::VariableType::tStruct);
message->structValue->emplace("payload", std::make_shared<Flows::Variable>("Synchronous sending."));
output(0, message, true);
Now Node-BLUE waits for each node path to finish before the next output is executed and causes all outputs of all following nodes to be executed in sequence. Please note that the synchronous sequence is broken by PHP nodes. It works for the PHP node itself but not for nodes following it. Link nodes also break synchronous output, but the output is synchronous up to the link node.
Setting status¶
Whilst running, a node is able to share status information with the editor UI. This is done by calling the nodeEvent
function:
Flows::PVariable status = std::make_shared<Flows::Variable>(Flows::VariableType::tStruct);
status->structValue->emplace("text", std::make_shared<Flows::Variable>("connected"));
status->structValue->emplace("fill", std::make_shared<Flows::Variable>("green"));
status->structValue->emplace("shape", std::make_shared<Flows::Variable>("dot"));
nodeEvent("status/" + _id, status);
Flows::Variable¶
Values in nodes are represented by the type Flows::Variable
. Any RPC function call and most methods in Flows::INode
use this type. It is able to represent every data type required for RPC or required to communicate with code written in other programming languages. The supported types are listed in the enumeration Flows::VariableType
:
enum class VariableType {
tVoid = 0x00,
tInteger = 0x01,
tBoolean = 0x02,
tString = 0x03,
tFloat = 0x04,
tArray = 0x100,
tStruct = 0x101,
//rpcDate = 0x10,
tBase64 = 0x11,
tBinary = 0xD0,
tInteger64 = 0xD1,
tVariant = 0x1111,
};
The constructors of Flows::Variable
accept most primary data types. The value can be accessed by using:
stringValue
integerValue
integerValue64
floatValue
booleanValue
arrayValue
structValue
binaryValue
Flows::Variable
is almost always used as a shared pointer. The shared pointer is represented by Flows::PVariable
.
Error Structs¶
Error Structs are Structs with the two properties faultCode
of type integer
and faultString
. In addition the flag errorStruct
must be set. The static method PVariable createError(int32_t faultCode, std::string faultString)
can be used to easily construct an error Struct.
Comparisons¶
All comparison operators are implemented for Flows::Variable
. It can also be evaluated as a boolean value. The latter basically checks if it contains a non-empty, non-zero value.
Copying¶
Flows::Variable
is fully copyable.
Printing¶
You can call print
to get a String representation of the value.
Providing RPC methods callable by other nodes¶
You can implement your own RPC methods in your node that can be called by other nodes. This is especially useful when providing a shared connection in a configuration node that should be used by other nodes.
Implementing the RPC method¶
The RPC method is just a normal method within your node:
//Header file
Flows::PVariable myRpcMethod(const Flows::PArray& parameters);
//Source file
Flows::PVariable MyNode::myRpcMethod(const Flows::PArray ¶meters) {
try {
//Check parameters for validity
//Do something
//You can also return a value
return std::make_shared<Flows::Variable>();
}
catch (const std::exception &ex) {
_out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what());
}
//Always return -32500 for application errors
return Flows::Variable::createError(-32500, "Unknown application error.");
}
This method needs to be added to _localRpcMethods
which is provided by the base class:
_localRpcMethods.emplace("myRpcMethod", std::bind(&MyNode::myRpcMethod, this, std::placeholders::_1));
That's it. Now this method can be called. You can test this from your shell with:
homegear -e rc 'print_v($hg->invokeNodeMethod("ID of your node", "myRpcMethod", ["my parameter"]))'
Calling the RPC method¶
The RPC method can be called from other nodes using invokeNodeMethod
.
/*
* The first parameter is the node ID to call the method on.
* The second parameter is the method's name.
* The third parameter are the parameters.
* The fourth parameter should be set to false if you don't need the return value. This increases performance.
*/
auto parameters = std::make_shared<Flows::Array>();
parameters->emplace_back(std::make_shared<Flows::Variable>("my parameter"));
auto result = invokeNodeMethod(myNodeId, 'myRpcMethod', parameters, true);
Flows::NodeInfo¶
An object of type Flows::NodeInfo
is passed to Flows::INode::init
and Flows::INode::input
. This contains the following information:
Variable | Description |
---|---|
std::string id |
The node's ID |
std::string flowId |
The flow's ID |
std::string type |
The type of the node |
Flows::PVariable info |
The full JSON of the node configuration including all settings, wires, etc. |
std::vector<std::vector<Wire>> wiresIn |
All incoming wires. The outer vector are the inputs, the inner vector the wires to that input. |
std::vector<std::vector<Wire>> wiresOut |
All outgoing wires. The outer vector are the outputs, the inner vector the wires from that output. |
Flows::MessageProperty¶
This class helps you working with inputs where a user can enter a message property (for example in the switch node). For example:
You can now construct the message property class with this setting:
auto property = Flows::MessageProperty("payload[5].data");
Now when a message arrives, you can extract that property:
auto myData = property.match(message);
//myData now contains the value of "payload[5].data"
In addition Flows::MessageProperty
provides the following methods:
Method | Description |
---|---|
bool empty(); |
Just returns if a non-empty property was passed to the constructor. |
bool erase(Flows::PVariable &message); |
Deletes the property specified in the constructor from message . |
bool set(Flows::PVariable &message, Flows::PVariable &value); |
Sets the property in message to value . |
Flows::JsonEncoder¶
Converts Flows::PVariable
to a JSON string (std::string Flows::JsonEncoder::getString(const PVariable &variable)
) or a JSON character vector (std::vector<char> Flows::JsonEncoder::getVector(const PVariable &variable)
). Both methods can be accessed statically.
Flows::JsonDecoder¶
Converts a JSON string (PVariable decode(const std::string &json)
) or character vector (PVariable decode(const std::vector<char> &json)
) to Flows::PVariable
. Both methods can be accessed statically.
Flows::Math¶
The Flows::Math
class implements methods
- to convert strings to numbers
- scale or clamp numbers
- convert floating point numbers to and from binary and
- to convert floating point numbers to strings with defined precision.
Look at the header file in libhomegear-node
for a description of the available methods.
Flows::HelperFunctions¶
The Flows::HelperFunctions
class implements methods:
- to trim Strings
- convert Strings to upper or lower case
- get the hexadecimal string representation of binary data
- get the current time in seconds, milliseconds, microseconds or a date and time String
For a description of the available methods see the header file in libhomegear-node
.
Flows::INode¶
Warning
Please note, that no methods except waitForStup
are allowed to block. Too many blocking calls would cause the Node-BLUE process to hang. There is no hard time limit, but blocking for 1 second already is too long. Consider moving long-running operations to a seperate thread.
Variables¶
The following variables can be accessed from within you node class:
Variable | Description |
---|---|
std::string _path |
The full path to the node's files. |
std::string _type |
The node type. |
std::string _id |
The node's ID. |
std::string _flowId |
The node's flow ID. |
std::string _name |
The name of the node (if set) |
const std::atomic_bool *_frontendConnected |
true when a Web browser with an open Node-BLUE editor is currently connected to Homegear. |
std::map<...> _localRpcMethods |
A map to define RPC methods for inter-node communication. Used primarily to communicate with configuration nodes. |
Callable methods¶
The following methods are implemented by the base class and can be called from your node.
Events
subscribePeer
void subscribePeer(uint64_t peerId, int32_t channel = -1, const std::string &variable = "")
Call this method to enable receiving variable updates for peers with variableEvent
. This method can be called in init
.
Parameters
Parameter | Description |
---|---|
peerId |
The ID of the peer to receive updates for |
channel |
The channel of the peer to receive updates for. If not specified all updates for this peer are received. |
variable |
The variable name to receive updates for. If not specified all updates for the specified channel are received. |
unsubscribePeer
void unsubscribePeer(uint64_t peerId, int32_t channel = -1, const std::string &variable = "")
Call this method to unsubscribe from previously subscribed events again. You need to pass the same parameters as to subscribePeer
. You don't need to call this method for cleanup.
subscribeFlow
void subscribeFlow()
Call this method to enable receiving flow variable updates with flowVariableEvent
. This method can be called in init
.
unsubscribeFlow
void unsubscribeFlow()
Call this method to unsubscribe from previously subscribed events again. You don't need to call this method for cleanup.
subscribeGlobal
void subscribeFlow()
Call this method to enable receiving Node-BLUE global variable updates with globalVariableEvent
. This method can be called in init
.
unsubscribeGlobal
void unsubscribeFlow()
Call this method to unsubscribe from previously subscribed events again. You don't need to call this method for cleanup.
subscribeHomegearEvents
void subscribeHomegearEvents()
Call this method to enable receiving Homegear events with homegearEvent
. This method can be called in init
.
unsubscribeHomegearEvents
void unsubscribeHomegearEvents()
Call this method to unsubscribe from previously subscribed events again. You don't need to call this method for cleanup.
subscribeStatusEvents
void subscribeStatusEvents()
Call this method to enable receiving status update events with statusEvent
. This method can be called in init
.
unsubscribeStatusEvents
void unsubscribeStatusEvents()
Call this method to unsubscribe from previously subscribed events again. You don't need to call this method for cleanup.
subscribeErrorEvents
void subscribeStatusEvents()
Call this method to enable receiving error events with errorEvent
. This method can be called in init
.
unsubscribeErrorEvents
void unsubscribeErrorEvents()
Call this method to unsubscribe from previously subscribed events again. You don't need to call this method for cleanup.
nodeEvent
void nodeEvent(const std::string &topic, const PVariable &value, bool retain)
Primarily nodeEvent
is used to set the status of a node. See here.
You can also send events to your node. The debug node for example is doing that. In the HTML file call RED.comms.subscribe("my-topic", function(value) {});
and RED.comms.unsubscribe("my-topic ", function(value) {})
. Then with nodeEvent("my-topic", my-data)
you can send something to the node.
Error handling
log
void log(int32_t logLevel, const std::string &message)
See "handling errors and logging events".
frontendEventLog
void frontendEventLog(const std::string &message)
See "handling errors and logging events".
Context
PVariable getNodeData(const std::string &key)
void setNodeData(const std::string &key, const PVariable &value)
PVariable getFlowData(const std::string &key)
void setFlowData(const std::string &key, const PVariable &value)
PVariable getGlobalData(const std::string &key)
void setGlobalData(const std::string &key, const PVariable &value)
See section "node context" for more information.
Message processing / communication / RPC
getConfigParameter
PVariable getConfigParameter(const std::string &nodeId, const std::string &name)
See "configuration nodes".
invoke
PVariable invoke(const std::string &methodName, const PArray ¶meters)
See "calling Homegear RPC methods".
invokeNodeMethod
PVariable invokeNodeMethod(const std::string &nodeId, const std::string &methodName, const PArray ¶meters, bool wait)
With invokeNodeMethod
you can call RPC methods provided by other nodes. See "providing RPC methods callable by other nodes".
output
void output(uint32_t outputIndex, const PVariable &message, bool synchronous = false)
See "sending messages".
setInternalMessage
void setInternalMessage(const PVariable &message)
Using this method you can attach undeletable data to the message object. Even if the message object is newly created within a node, the internal message is attached again to the message.
Overridable methods¶
Methods used for initialization and deinitialization
See section initialization and deinitialization for more information.
Method |
---|
bool init(const PNodeInfo &nodeInfo); |
bool start(); |
void configNodesStarted(); |
void startUpComplete(); |
void stop(); |
void waitForStop(); |
Event methods
input
virtual void input(const PNodeInfo &nodeInfo, uint32_t index, const PVariable &message)
input
is called every time a message arrives. It is protected by a mutex, so you don't need to worry about parallel calls to this method.
Parameters
Parameter | Description |
---|---|
nodeInfo |
See here |
index |
The index of the input the message arrived at |
message |
The message object |
variableEvent
virtual void variableEvent(const std::string &source,
uint64_t peerId,
int32_t channel,
const std::string &variable,
const PVariable &value,
const PVariable &metadata)
This method is called on any variable update to system variables, metadata variables and subscribed peer variables. To get peer variable updates, you need to subscribe them by calling subscribePeer
. As subscribePeer
doesn't require inter-process or inter-node communication, it already can be called within init
.
Note
System variables and metadata variables don't require subscription.
Parameters
Parameter | Description |
---|---|
source |
The entity or service where the event originated: device-<peer ID> , scriptEngine , profileManager , nodeBlue , ipcServer , homegear , client-<ID> , rpc-client-<ID> or mqtt |
peerId |
The ID of the peer the variable changed for. 0 for system variables |
channel |
The channel of the peer the variable changed for. -1 for metadata or system variables |
variable |
The name of the changed variable |
value |
The new value |
metadata |
Name of the peer; room, category and role information |
flowVariableEvent
virtual void flowVariableEvent(const std::string &flowId, const std::string &variable, const PVariable &value)
flowVariableEvent
is called on any flow variable update within the flow the notified node is in. To receive flow variable events, you need to subscribe them calling subscribeFlow()
(can already be called within init
).
Parameters
Parameter | Description |
---|---|
flowId |
The ID of the flow. As the flow ID already is known this is redundant information |
variable |
The name of the flow variable |
value |
The new value |
globalVariableEvent
virtual void globalVariableEvent(const std::string &variable, const PVariable &value)
globalVariableEvent
is called on any Node-BLUE global variable update. To receive global variable events, you need to subscribe them calling subscribeGlobal()
(can already be called within init
).
Parameters
Parameter | Description |
---|---|
variable |
The name of the global variable |
value |
The new value |
homegearEvent
virtual void homegearEvent(const std::string &type, const PArray &data)
This is a "catch all" event handler. Every event handled by the Node-BLUE process triggers a call to this method. Use this method only when really required. To receive these events, you need to subscribe them by calling subscribeHomegearEvents()
.
Parameters
Parameter | Description |
---|---|
type |
The type of event: deviceVariableEvent , metadataVariableEvent , systemVariableEvent , flowVariableEvent , globalVariableEvent , variableProfileStateChanged , uiNotificationCreated , uiNotificationRemoved , uiNotificationAction , newDevices , deleteDevices , updateDevice or rawPacketEvent . |
data |
The event-specific data. |
statusEvent
virtual void statusEvent(const std::string &nodeId, const PVariable &status)
When a node sets it's status a status event is triggered and statusEvent
is called for all subscribed nodes. You can subscribe for status events by calling subscribeStatusEvents()
(can already be called within init
).
Parameters
Parameter | Description |
---|---|
nodeId |
The ID of the node the status is updated for (not the node triggering the status update). |
status |
The status object |
errorEvent
virtual bool errorEvent(const std::string &nodeId, int32_t level, const PVariable &error)
The method errorEvent
is called for all subscribed nodes whenever a node logs an error or warning. You can subscribe for error events by calling subscribeErrorEvents(bool catchConfigurationNodeErrors, bool hasScope, bool ignoreCaught)
(can already be called within init
).
Parameters
Parameter | Description |
---|---|
nodeId |
The ID of the node logging the error |
level |
10 for critical, 20 for error, 30 for warning (Homegear's log level times 10) |
error |
The error object containing information about the node and the error message |
Other methods
getNodeVariable
virtual PVariable getNodeVariable(const std::string &variable)
When this method is overridden, it is called whenever the RPC method getNodeVariable
is called for the implementing node.
setNodeVariable
virtual void setNodeVariable(const std::string &variable, const PVariable &value)
When this method is overridden, it is called whenever the RPC method setNodeVariable
is called for the implementing node.
getConfigParameterIncoming
virtual PVariable getConfigParameterIncoming(const std::string &name)
See "configuration nodes".
invokeLocal
virtual PVariable invokeLocal(const std::string &methodName, const PArray ¶meters)
Calls a RPC method implemented in the current class. This method can be overridden, but the default implementation should be fine in most cases. See