{"id":13766494,"url":"https://github.com/QUaServer/QUaServer","last_synced_at":"2025-05-10T22:31:14.289Z","repository":{"id":36697939,"uuid":"180606564","full_name":"QUaServer/QUaServer","owner":"QUaServer","description":"Qt C++ wrapper for open62541 server stack","archived":false,"fork":false,"pushed_at":"2024-02-13T18:21:05.000Z","size":8568,"stargazers_count":110,"open_issues_count":6,"forks_count":43,"subscribers_count":14,"default_branch":"master","last_synced_at":"2024-04-14T09:03:20.982Z","etag":null,"topics":["cplusplus","cpp","opc-ua","qt","qt5"],"latest_commit_sha":null,"homepage":null,"language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/QUaServer.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2019-04-10T15:05:50.000Z","updated_at":"2024-04-19T08:53:36.336Z","dependencies_parsed_at":"2024-04-19T08:53:33.856Z","dependency_job_id":"5988a17c-7aaf-447c-9af2-cdf53854ea67","html_url":"https://github.com/QUaServer/QUaServer","commit_stats":null,"previous_names":["juangburgos/quaserver"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/QUaServer%2FQUaServer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/QUaServer%2FQUaServer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/QUaServer%2FQUaServer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/QUaServer%2FQUaServer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/QUaServer","download_url":"https://codeload.github.com/QUaServer/QUaServer/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253492529,"owners_count":21916959,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cplusplus","cpp","opc-ua","qt","qt5"],"created_at":"2024-08-03T16:00:56.103Z","updated_at":"2025-05-10T22:31:11.876Z","avatar_url":"https://github.com/QUaServer.png","language":"C++","funding_links":[],"categories":["Software"],"sub_categories":["SDKs and Libraries"],"readme":"# QUaServer\n\nThis is a [Qt](https://www.qt.io/) based library that provides a C++ **wrapper** for the [open62541](https://open62541.org/) library, and **abstraction** for the OPC UA Server API.\n\nBy *abstraction* it is meant that some of flexibility provided by the original *open62541* server API is sacrificed for ease of use. If more flexibility is required than what *QUaServer* provides, it is highly recommended to use the original *open62541* instead.\n\nThe main goal of this library is to provide an object-oriented API that allows quick prototyping for OPC UA servers without having to spend much time in creating complex address space structures.\n\n*QUaServer* is still work in progress, test properly and use precaution before using in production. Please report any issues you encounter in this repository providing a minimum working code example that replicates the issue and a thorough description.\n\n```\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n```\n\nTo test a *QUaServer* based application it is recommended to use the [UA Expert](https://www.unified-automation.com/downloads/opc-ua-clients.html) OPC UA Client.\n\n## Table of Contents  \n\n* [Include](#Include)\n\nHow to include QUaServer in your Qt project.\n\n* [Basics](#Basics)\n\nHow to create object and variable instances and set their attributes.\n\n* [Methods](#Methods)\n\nHow to create methods on the server than can be remotelly called from any client.\n\n* [References](#References)\n\nHow to create *non-hierarchical* references between nodes and browse such relations.\n\n* [Types](#Types)\n\nHow to create custom object and variable types.\n\n* [Server](#Server)\n\nHow to customize the server's properties.\n\n* [Users](#Users)\n\nHow to implement access control for the server based on user permissions.\n\n* [Encryption](#Encryption)\n\nHow to enable and configure secure encrypted communication between the server and clients.\n\n* [Events](#Events)\n\nHow to create and emit custom events.\n\n* [Serialization](#Serialization)\n\nHow to serialize and deserialize the server address space to and from disk.\n\n* [Historizing](#Historizing)\n\nHow to enable and store historical data and historical events.\n\n* [Alarms](#Alarms)\n\nHow to create server alarms and conditions.\n\n---\n\n## Include\n\nThis library requires at least `Qt 5.9` or higher and `C++ 11`.\n\nTo use *QUaServer*, first a copy of the *open62541* shared library is needed. The [open62541 repo](https://github.com/open62541/open62541) is included in this project as a **git submodule** ([`./depends/open62541.git`](./depends/open62541.git)). So don't forget to clone this repository **recursively**, or run `git submodule update --init --recursive` after cloning this repo.\n\nThe *open62541* amalgamation on can be created using the following *QMake* command on the [amalgamation project](./src/amalgamation) included in this repo:\n\n```bash\ncd ./src/amalgamation\n# Windows\nqmake -tp vc amalgamation.pro\nmsbuild open62541.vcxproj /p:Configuration=Debug\nmsbuild open62541.vcxproj /p:Configuration=Release\n# Linux\nqmake amalgamation.pro\nmake all\n```\n\nThe [`./depends/open62541.git`](./depends/open62541.git) submodule on this repo (used in the [amalgamation project](./src/amalgamation)), tracks the latest **compatible** *open62541* version, which might not be the most recent version of their master branch. Compatibility of *QUaServer* with the latest version of *open62541* is not always guaranteed.\n\nAfter compiling the amalgamation, to include *QUaServer* in your project, just include [./src/wrapper/quaserver.pri](./src/wrapper/quaserver.pri) into your Qt project file (`*.pro` file). For example:\n\n```cmake\nQT += core\nQT -= gui\n\nCONFIG += c++11\n\nTARGET = my_project\nCONFIG += console\nCONFIG -= app_bundle\n\nTEMPLATE = app\n\nINCLUDEPATH += $$PWD/\n\nSOURCES += main.cpp\n\ninclude($$PWD/../../src/wrapper/quaserver.pri)\n```\n\n### Examples\n\nThis library comes with examples in the `./examples` folder, which are explained in detail throughout this document. To build the examples use the [`examples.pro`](./examples.pro) included in the root of this repository:\n\n```bash\n# Windows\nqmake -r -tp vc examples.pro\nmsbuild examples.sln /p:Configuration=Debug\n# Linux\nqmake -r examples.pro\nmake all\n```\n\n---\n\n## Basics\n\nTo start using *QUaServer* it is necessary to include the `QUaServer` header as follows:\n\n```c++\n#include \u003cQUaServer\u003e\n```\n\nTo create a server simply create an `QUaServer` instance and call the `start()` method:\n\n```c++\nint main(int argc, char *argv[])\n{\n\tQCoreApplication a(argc, argv);\n\n\t// create server\n\tQUaServer server;\n\t// start server\n\tserver.start();\n\n\treturn a.exec(); \n}\n```\n\nNote it is necessary to create a `QCoreApplication` and execute it, because `QUaServer` makes use of [Qt's event loop](https://wiki.qt.io/Threads_Events_QObjects).\n\nBy default the *QUaServer* listens on port **4840** which is the [IANA assigned port](https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml?search=4840) for OPC UA applications. To change the listening port, simply pass call the `setPort` method **before** starting the server:\n\n```c++\nserver.setPort(8080);\n```\n\nTo start creating OPC *Objects* and *Variables* it is necessary to get the *Objects Folder* of the server and start adding instances to it:\n\n```c++\nint main(int argc, char *argv[])\n{\n\tQCoreApplication a(argc, argv);\n\n\tQUaServer server;\n\n\t// get objects folder\n\tQUaFolderObject * objsFolder = server.objectsFolder();\n\n\t// add some instances to the objects folder\n\tQUaBaseDataVariable * varBaseData = objsFolder-\u003eaddBaseDataVariable(\"my_variable\");\n\tQUaProperty         * varProp     = objsFolder-\u003eaddProperty(\"my_property\");\n\tQUaBaseObject       * objBase     = objsFolder-\u003eaddBaseObject(\"my_object\");\n\tQUaFolderObject     * objFolder   = objsFolder-\u003eaddFolderObject(\"my_folder\");\n\n\t// set values to variables\n\tvarBaseData-\u003esetValue(1);\n\tvarProp-\u003esetValue(\"hola\");\n\n\tserver.start();\n\n\treturn a.exec(); \n}\n```\n\nInstances must only be added using the *QUaServer* API, by using the following methods:\n\n* `addProperty` : Adds a `QUaProperty` instance. [*Properties*](https://reference.opcfoundation.org/v104/Core/docs/Part3/4.4.2/) are the **leaves** of the Address Space tree and cannot have other children. They are used to charaterise what its parent represents and their value do not change often. For example, an *engineering unit* or a *brand name*.\n\n* `addBaseDataVariable` : Adds a `QUaBaseDataVariable` instance. [*BaseDataVariables*](https://reference.opcfoundation.org/v104/Core/docs/Part3/5.6.4/) are used to hold data which might change often and can have children (*Objects*, *Properties*, other *BaseDataVariables*). An example is the *current value* of a temperature sensor.\n\n* `addBaseObject` : Adds a `QUaBaseObject` instance. [*BaseObjects*](https://reference.opcfoundation.org/v104/Core/docs/Part3/5.5.1/) can have children and are used to organize other *Objects*, *Properties*, *BaseDataVariables*, etc. The purpose of objects is to **model** a real device. For example a temperature sensor which has *engineering unit* and *brand name* as properties and *current value* as a variable.\n\n* `addFolderObject` : Adds a `QUaFolderObject` instance. [*FolderObjects*](https://reference.opcfoundation.org/v104/Core/docs/Part3/5.5.3/) derive from *BaseObjects* and can do the same, but are typically use to organize a collection of objects. The so called **Objects Folder** is a `QUaFolderObject` instance that always exists on the server to serve as a container for all the user instances.\n\nOnce connected to the server, the [address space](https://reference.opcfoundation.org/v104/Core/docs/Part1/6.3.4/) should look something like this:\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"./res/img/01_basics_02.jpg\"\u003e\n\u003c/p\u003e\n\nThe string argument passed to these methods defines both the node's initial [`DisplayName`](https://reference.opcfoundation.org/v104/Core/docs/Part3/5.2.5/) and [`BrowseName`](https://reference.opcfoundation.org/v104/Core/docs/Part3/5.2.4/). The `DisplayName` is the name that is displayed to the user by client applications, it should be a human-friendly name. The `BrowseName` is the name that is used programmatically by client applications to find children nodes easily in hierarchical node structures. The `BrowseName` is **inmutable** once a node instance has been created, and must be **unique** with respect to its parent, so one parent node cannot have multiple children with the same `BrowseName`. The `DisplayName` has no restrictions, so it can be changed programmatically.\n\nThe `Value` is also set for the variables defined in the example above. The `DataType` of the `Value` is inferred automatically by `QUaServer`, but it can also be set explicitly as it will be shown later.\n\nThe `DisplayName`, `BrowseName`, `Value` and `DataType` are [**OPC Attributes**](https://reference.opcfoundation.org/v104/Robotics/docs/3.4.3/). Depending on the type of the instance (*Properties*, *BaseDataVariables*, etc.) it is possible to set different attributes. All OPC instance types derive from the [**Node**](https://reference.opcfoundation.org/v104/Core/docs/Part3/4.3.1/) type. Similarly, in *QUaServer*, all the types derive directly or indirectly from the C++ `QUaNode` abstract class. \n\nThe *QUaServer* API allows to read and write the instances attributes with the following methods:\n\n### For all Types\n\nThe *QUaNode* API provides the following methods to access attributes:\n\n```c++\nQUaLocalizedText displayName   () const;\nvoid             setDisplayName(const QUaLocalizedText \u0026displayName);\n\nQUaLocalizedText description   () const;\nvoid             setDescription(const QUaLocalizedText \u0026description);\n\nquint32 writeMask     () const;\nvoid    setWriteMask  (const quint32 \u0026writeMask);\n\nQUaNodeId nodeId() const;\n\nQString nodeClass() const;\n\nQUaQualifiedName browseName() const;\n```\n\nThe `nodeId()` method returns an object containing the [**NodeId**](https://reference.opcfoundation.org/v104/Core/docs/Part3/5.2.2/), which is a unique identifier of the node. This is the only **unique** identifier of a node within a server, because neither the `BrowseName` nor `DisplayName` attributes are unique.\n\nBy default a random `NodeId` is assigned automatically when creating a node instance. It is possible to define a custom `NodeId` upon instantiation by passing the string *XML notation* as the *second* argument to the respective method. If the NodeId is invalid or already exists, creating the instance will fail returning `nullptr`. For example:\n\n```c++\nQUaProperty * varProp = objsFolder-\u003eaddProperty(\"my_property\", \"ns=1;s=my_prop\");\nif(!varProp)\n{\n\tqDebug() \u003c\u003c \"Creating instance failed!\";\n}\n```\n\nTo notify changes, the *QUaNode* API provides the following *Qt signals*:\n\n```c++\nvoid displayNameChanged(const QUaLocalizedText \u0026displayName);\nvoid descriptionChanged(const QUaLocalizedText \u0026description);\nvoid writeMaskChanged  (const quint32 \u0026writeMask);\n```\n\nFurthermore, the API also notifies when a child is added to an *QUaBaseObject* or *QUaBaseDataVariable* instance:\n\n```c++\nvoid childAdded(QUaNode * childNode);\n```\n\n### For Variable Types\n\nBoth `QUaBaseDataVariable` and `QUaProperty` derive from the abstract C++ class `QUaBaseVariable` which provides the following methods to access the variable's [**attributes**](https://reference.opcfoundation.org/v104/Core/docs/Part3/5.6.2/):\n\n```c++\nQVariant          value() const;\nvoid              setValue(const QVariant \u0026value);\n\nQDateTime         sourceTimestamp() const;\nvoid              setSourceTimestamp(const QDateTime\u0026 sourceTimestamp);\n\nQDateTime         serverTimestamp() const;\nvoid              setServerTimestamp(const QDateTime\u0026 serverTimestamp);\n\nQUaStatusCode     statusCode() const;\nvoid              setStatusCode(const QUaStatusCode\u0026 statusCode);\n\nQMetaType::Type   dataType() const;\nvoid              setDataType(const QMetaType::Type \u0026dataType);\n\nqint32            valueRank() const;\nQVector\u003cquint32\u003e  arrayDimensions() const; \n\nquint8            accessLevel() const;\nvoid              setAccessLevel(const quint8 \u0026accessLevel);\n\ndouble            minimumSamplingInterval() const;\nvoid              setMinimumSamplingInterval(const double \u0026minimumSamplingInterval);\n\nbool              historizing() const;\n```\n\nThe `value`, `sourceTimestamp`, `serverTimestamp` and `statusCode` can be set in a single call by using the `setValue` overload:\n\n```c++\nvoid setValue(\n\tconst QVariant \u0026value, \n\tconst QUaStatusCode   \u0026statusCode,\n\tconst QDateTime       \u0026sourceTimestamp,\n\tconst QDateTime       \u0026serverTimestamp\n);\n```\n\nThe `setDataType()` can be used to *force* a data type on the variable value. The following [Qt types](https://doc.qt.io/qt-5/qmetatype.html#Type-enum) are supported, as well as their `QList\u003cT\u003e` and `QVector\u003cT\u003e` types:\n\n```c++\nQMetaType::Bool\nQMetaType::Char\nQMetaType::SChar\nQMetaType::UChar\nQMetaType::Short\nQMetaType::UShort\nQMetaType::Int\nQMetaType::UInt\nQMetaType::Long\nQMetaType::LongLong\nQMetaType::ULong\nQMetaType::ULongLong\nQMetaType::Float\nQMetaType::Double\nQMetaType::QString\nQMetaType::QDateTime\nQMetaType::QUuid\nQMetaType::QByteArray\n```\n\nThe `setAccessLevel()` method allows to set a bit mask to define the overall variable read and write access. Nevertheless, the `QUaBaseVariable` API provides a couple of helper methods that allow to define the access more easily without needing to deal with bit masks:\n\n```c++\n// Default : read access true\nbool readAccess() const;\nvoid setReadAccess(const bool \u0026readAccess);\n// Default : write access false\nbool writeAccess() const;\nvoid setWriteAccess(const bool \u0026writeAccess);\n```\n\nUsing such methods we could set a variable to be **writable**, for example:\n\n```c++\nQUaBaseDataVariable * varBaseData = objsFolder-\u003eaddBaseDataVariable();\nvarBaseData-\u003esetWriteAccess(true);\n```\n\nWhen a variable is written from a client, on the server notifications are provided by the `void QUaBaseVariable::valueChanged(const QVariant \u0026value, const bool \u0026networkChange)` Qt signal.\n\n```c++\nQObject::connect(varBaseData, \u0026QUaBaseDataVariable::valueChanged, [](const QVariant \u0026value, const bool \u0026networkChange) {\n\tqDebug() \u003c\u003c \"New value :\" \u003c\u003c value \u003c\u003c (networkChange ? \"(Network)\" : \"(Server Logic)\");\n});\n```\n\nThe `networkChange` argument specifies if the value was changed though the network by an OPC client or if the value change was performed programmatically by internal the server logic.\n\n### For Object Types\n\nThe API provides the following methods to access attributes:\n\n```c++\nquint8 eventNotifier() const;\nvoid   setEventNotifier(const quint8 \u0026eventNotifier);\n\n#ifdef UA_ENABLE_SUBSCRIPTIONS_EVENTS\nbool subscribeToEvents() const;\nvoid setSubscribeToEvents(const bool\u0026 subscribeToEvents);\n#endif // UA_ENABLE_SUBSCRIPTIONS_EVENTS\n```\n\nThe usage of these methods is described in detail in the *Events* section.\n\n### Basics Example\n\nBuild and test the basics example in [./examples/01_basics](./examples/01_basics/main.cpp) to learn more.\n\n---\n\n## Methods\n\nIn OPC UA, *BaseObjects* instances can have methods. To support this, the *QUaBaseObject* API has the `addMethod()` method which allows to define a name for the method and a callback.\n\nSince the *Objects Folder* is an instance of `QUaBaseObject`, it is possible to add methods to it directly, for example:\n\n```c++\nint addNumbers(int x, int y)\n{\n\treturn x + y;\n}\n\nint main(int argc, char *argv[])\n{\n\tQCoreApplication a(argc, argv);\n\n\tQUaServer server;\n\n\tQUaFolderObject * objsFolder = server.objectsFolder();\n\n\t// add a method using callback function\n\tobjsFolder-\u003eaddMethod(\"addNumbers\", \u0026addNumbers);\n\n\tserver.start();\n\n\treturn a.exec(); \n}\n``` \n\nWhich can be remotely executed using a client.\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"./res/img/02_methods_01.jpg\"\u003e\n\u003c/p\u003e\n\nNote that the *QUaServer* library automatically deduces the arguments and return types. But only the types supported by the `setDataType()` method (see the *Basics* section) are supported by the `addMethod()` API.\n\nA more *flexible* way of adding methods is by using **C++ Lambdas**:\n\n```c++\nobjsFolder-\u003eaddMethod(\"increaseNumber\", [](double input) {\n\tdouble increment = 0.1;\n\treturn input + increment;\n});\n```\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"./res/img/02_methods_02.jpg\"\u003e\n\u003c/p\u003e\n\nUsing the *Lambda Capture* it is possible to change *Objects* or *Variables*:\n\n```c++\nauto varNumber = objsFolder-\u003eaddBaseDataVariable();\nvarNumber-\u003esetDisplayName(\"Number\");\nvarNumber-\u003esetValue(0.0);\nvarNumber-\u003esetDataType(QMetaType::Double);\n\nobjsFolder-\u003eaddMethod(\"incrementNumberBy\", [\u0026varNumber](double increment) {\n\tdouble currentValue = varNumber-\u003evalue().toDouble();\n\tdouble newValue = currentValue + increment;\n\tvarNumber-\u003esetValue(newValue);\n\treturn true;\n});\n```\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"./res/img/02_methods_03.jpg\"\u003e\n\u003c/p\u003e\n\nUsing methods we can even **delete** *Objects* or *Variables*:\n\n```c++\nobjsFolder-\u003eaddMethod(\"deleteNumber\", [\u0026varNumber]() {\n\tif (!varNumber)\n\t{\n\t\treturn;\n\t}\n\tdelete varNumber;\n\tvarNumber = nullptr;\n});\n```\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"./res/img/02_methods_04.jpg\"\u003e\n\u003c/p\u003e\n\n### Methods Example\n\nBuild and test the methods example in [./examples/02_methods](./examples/02_methods/main.cpp) to learn more.\n\n---\n\n## References\n\nOPC UA supports the concept of *References* to create relations between *Nodes*. References are categorised in *HierarchicalReferences* and *NonHierarchicalReferences*. The *HierarchicalReferences* are the ones used by most OPC Clients to display the instances tree in their graphical user interfaces.\n\nWhen adding an instance using the *QUaServer* API, the library creates the required *HierarchicalReference* type necessary to display the new instance in the instances tree (it uses the *HasComponent*, *HasProperty* or *Organizes* reference types accordingly).\n\nThe *QUaServer* API also allows to create **custom** *NonHierarchicalReferences* that can be used to create custom relations between instances. For example, having a temperature sensor and then define a supplier for that sensor:\n\n```c++\n// create sensor\nQUaBaseObject * objSensor1 = objsFolder-\u003eaddBaseObject(\"TempSensor1\");\n// create supplier\nQUaBaseObject * objSupl1 = objsFolder-\u003eaddBaseObject(\"Mouser\");\n// create reference\nserver.registerReference({ \"Supplies\", \"IsSuppliedBy\" });\nobjSupl1-\u003eaddReference({ \"Supplies\", \"IsSuppliedBy\" }, objSensor1);\n```\n\nThe `registerReference()` method has to be called in order to *register* the new reference type as a *subtype* of the *NonHierarchicalReferences*. If the reference type is not registered before its first use, it is registered automatically on first use. \n\nThe registered reference can be observed when the server is running by browsing to `/Root/Types/ReferenceTypes/NonHierarchicalReferences`. There should be a new entry corresponding to the custom reference.\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"./res/img/03_references_01.jpg\"\u003e\n\u003c/p\u003e\n\nThe references for the supplier object should list the *Supplies* reference:\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"./res/img/03_references_02.jpg\"\u003e\n\u003c/p\u003e\n\nThe references for the sensor object should list the *IsSuppliedBy* reference:\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"./res/img/03_references_03.jpg\"\u003e\n\u003c/p\u003e\n\nThe `registerReference()` actually receives a `QUaReference` instance as an argument, which is defined as:\n\n```c++\nstruct QUaReference\n{\n\tQString strForwardName;\n\tQString strInverseName;\n};\n```\n\nBoth **forward** and **reverse** names of the reference have to be defined in order to create the reference. In the example, `Supplies` is the *forward* name, and `IsSuppliedBy` is the reverse name. When adding a reference, by default, it is added in *forward* mode. This can be changed by adding a third argument to the `addReference()` method which is `true` by default to indicate it is *forward*, `false` to indicate it is *reverse*. \n\n```c++\n// objSupl1 \"Supplies\" objSensor1\nobjSupl1-\u003eaddReference({ \"Supplies\", \"IsSuppliedBy\" }, objSensor1, true);\n// objSensor2 \"IsSuppliedBy\" objSupl1\nobjSensor2-\u003eaddReference({ \"Supplies\", \"IsSuppliedBy\" }, objSupl1, false);\n```\n\nIn the example above, both sensors are supplied by the same supplier:\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"./res/img/03_references_04.jpg\"\u003e\n\u003c/p\u003e\n\nProgrammatically, references can be added, removed and browsed using the following *QUaNode* API methods:\n\n```c++\nvoid addReference(const QUaReference \u0026ref, const QUaNode * nodeTarget, const bool \u0026isForward = true);\n\nvoid removeReference(const QUaReference \u0026ref, const QUaNode * nodeTarget, const bool \u0026isForward = true);\n\ntemplate\u003ctypename T\u003e\nQList\u003cT*\u003e       findReferences(const QUaReference \u0026ref, const bool \u0026isForward = true);\n// specialization\nQList\u003cQUaNode*\u003e findReferences(const QUaReference \u0026ref, const bool \u0026isForward = true);\n```\n\nFor example, to list all the sensors that are supplied by the supplier:\n\n```c++\nqDebug() \u003c\u003c \"Supplier\" \u003c\u003c objSupl1-\u003edisplayName() \u003c\u003c \"supplies :\";\nauto listSensors = objSupl1-\u003efindReferences\u003cQUaBaseObject\u003e({ \"Supplies\", \"IsSuppliedBy\" });\nfor (int i = 0; i \u003c listSensors.count(); i++)\n{\n\tqDebug() \u003c\u003c listSensors.at(i)-\u003edisplayName();\n}\n```\n\nAnd to list the supplier of a sensor:\n\n```c++\nqDebug() \u003c\u003c objSensor1-\u003edisplayName() \u003c\u003c \"supplier is: :\";\nauto listSuppliers = objSensor1-\u003efindReferences\u003cQUaBaseObject\u003e({ \"Supplies\", \"IsSuppliedBy\" }, false);\nqDebug() \u003c\u003c listSuppliers.first()-\u003edisplayName();\n```\n\nNote that when a *QUaNode* derived instance is deleted, all its references are removed.\n\nTo notify when a reference has been added or removed the *QUaNode* API has the following *Qt signals*:\n\n```c++\nvoid referenceAdded  (const QUaReference \u0026 ref, QUaNode * nodeTarget, const bool \u0026isForward);\nvoid referenceRemoved(const QUaReference \u0026 ref, QUaNode * nodeTarget, const bool \u0026isForward);\n```\n\n### References Example\n\nBuild and test the methods example in [./examples/03_references](./examples/03_references/main.cpp) to learn more.\n\n---\n\n## Types\n\nOPC types can be extended by subtyping *BaseObjects* or *BaseDataVariables* (*Properties* cannot be subtyped). Using the *QUaServer* library, a new *BaseObject* subtype can be created by deriving from `QUaBaseObject`. Similarly, a new *BaseDataVariable* subtype can be created by deriving from `QUaBaseDataVariable`.\n\nSubtyping is very useful to **reuse** code. For example, if multiple temperature sensors are to be exposed through the OPC UA Server, it might be worth creating a type for it. Start by sub-classing `QUaBaseObject` as follows:\n\nIn `temperaturesensor.h` :\n\n```c++\n#include \u003cQUaBaseObject\u003e\n\nclass TemperatureSensor : public QUaBaseObject\n{\n\tQ_OBJECT\n\npublic:\n\tQ_INVOKABLE explicit TemperatureSensor(QUaServer *server);\n\t\n};\n```\n\nIn `temperaturesensor.cpp` :\n\n```c++\n#include \"temperaturesensor.h\"\n\nTemperatureSensor::TemperatureSensor(QUaServer *server)\n\t: QUaBaseObject(server)\n{\n\t\n}\n```\n\nThere are 3 **important requirements** when creating subtypes:\n\n* Inherit from either *QUaBaseObject* or *QUaBaseDataVariable* (which in turn inherit indirectly from *QObject*). The `Q_OBJECT` macro must be set.\n\n* Create a public constructor that receives a `QUaServer` pointer as an argument. Add the `Q_INVOKABLE` macro to such constructor.\n\n* In the constructor implementation call the parent constructor (*QUaBaseObject*, *QUaBaseDataVariable* or derived parent constructor accordingly).\n\nOnce all this is met, elsewhere in the code it is necessary to register the new type in the server using the `registerType\u003cT\u003e()` method. If not registered, then when creating an instance of the new type, the type will be registered automatically by the library.\n\nAn instance of the new type is created using the `addChild\u003cT\u003e()` method:\n\n```c++\n#include \"temperaturesensor.h\"\n\nint main(int argc, char *argv[])\n{\n\tQCoreApplication a(argc, argv);\n\n\tQUaServer server;\n\n\tQUaFolderObject * objsFolder = server.objectsFolder();\n\n\t// register new type\n\tserver.registerType\u003cTemperatureSensor\u003e();\n\n\t// create new type instance\n\tauto sensor1 = objsFolder-\u003eaddChild\u003cTemperatureSensor\u003e(\"Sensor1\");\n\n\tserver.start();\n\n\treturn a.exec(); \n}\n``` \n\nIf the new type was registered correctly, it can be observed by browsing to `/Root/Types/ObjectTypes/BaseObjectType`. There should be a new entry corresponding to the custom type.\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"./res/img/04_types_01.jpg\"\u003e\n\u003c/p\u003e\n\nNote that the new *TemperatureSensor* type has a `TypeDefinitionOf` reference to the *Sensor1* instance. And the *Sensor1* instance has a `HasTypeDefinition` to the *TemperatureSensor* type.\n\nAdding **child** *Variables*, *Properties* and potentially other *Objects* to the *TemperatureSensor* type is achieved through the [Qt Property System](https://doc.qt.io/qt-5/properties.html). \n\nUse the `Q_PROPERTY` macro to add pointers to types of desired children and the library will automatically instantiate the children once an specific instance of the *TemperatureSensor* type is created.\n\n```c++\n#include \u003cQUaBaseObject\u003e\n#include \u003cQUaBaseDataVariable\u003e\n#include \u003cQUaProperty\u003e\n\nclass TemperatureSensor : public QUaBaseObject\n{\n\tQ_OBJECT\n\t// properties\n\tQ_PROPERTY(QUaProperty * model READ model)\n\tQ_PROPERTY(QUaProperty * brand READ brand)\n\tQ_PROPERTY(QUaProperty * units READ units)\n\t// variables\n\tQ_PROPERTY(QUaBaseDataVariable * status       READ status      )\n\tQ_PROPERTY(QUaBaseDataVariable * currentValue READ currentValue)\npublic:\n\tQ_INVOKABLE explicit TemperatureSensor(QUaServer *server);\n\n\tQUaProperty * model();\n\tQUaProperty * brand();\n\tQUaProperty * units();\n\n\tQUaBaseDataVariable * status      ();\n\tQUaBaseDataVariable * currentValue();\n};\n```\n\nThe *QUaServer* library automatically adds the C++ instances as [QObject children](https://doc.qt.io/qt-5/objecttrees.html) of the *TemperatureSensor* instance and assigns them the `Q_PROPERTY` name as their [QObject name](https://doc.qt.io/Qt-5/qobject.html#objectName-prop). Therefore it is possible retrieve the C++ children using the [`findChild` method](https://doc.qt.io/Qt-5/qobject.html#findChild).\n\n```c++\nTemperatureSensor::TemperatureSensor(QUaServer *server)\n\t: QUaBaseObject(server)\n{\n\t// set defaults\n\tmodel()-\u003esetValue(\"TM35\");\n\tbrand()-\u003esetValue(\"Texas Instruments\");\n\tunits()-\u003esetValue(\"C\");\n\tstatus()-\u003esetValue(\"Off\");\n\tcurrentValue()-\u003esetValue(0.0);\n\tcurrentValue()-\u003esetDataType(QMetaType::Double);\n}\n\nQUaProperty * TemperatureSensor::model()\n{\n\treturn this-\u003ebrowseChild\u003cQUaProperty\u003e(\"model\");\n}\n\nQUaProperty * TemperatureSensor::brand()\n{\n\treturn this-\u003ebrowseChild\u003cQUaProperty\u003e(\"brand\");\n}\n\nQUaProperty * TemperatureSensor::units()\n{\n\treturn this-\u003ebrowseChild\u003cQUaProperty\u003e(\"units\");\n}\n\nQUaBaseDataVariable * TemperatureSensor::status()\n{\n\treturn this-\u003ebrowseChild\u003cQUaBaseDataVariable\u003e(\"status\");\n}\n\nQUaBaseDataVariable * TemperatureSensor::currentValue()\n{\n\treturn this-\u003ebrowseChild\u003cQUaBaseDataVariable\u003e(\"currentValue\");\n}\n```\n\nBe **careful** when using the `browseChild` to provide the correct `BrowseName`, otherwise a null reference can be returned from any of the getter methods.\n\nNote that in the *TemperatureSensor* constructor it is possible to already make use of the children instances and define some default values for them.\n\nNow it is possible to create any number of *TemperatureSensor* instances and their children will be created and attached to them automatically.\n\n```c++\nauto sensor1 = objsFolder-\u003eaddChild\u003cTemperatureSensor\u003e(\"Sensor1\");\nauto sensor2 = objsFolder-\u003eaddChild\u003cTemperatureSensor\u003e(\"Sensor2\");\nauto sensor3 = objsFolder-\u003eaddChild\u003cTemperatureSensor\u003e(\"Sensor3\");\n```\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"./res/img/04_types_02.jpg\"\u003e\n\u003c/p\u003e\n\nAny `Q_PROPERTY` added to the *TemperatureSensor* declaration that **inherits** `QUaProperty`, `QUaBaseDataVariable` or `QUaBaseObject` will be exposed through OPC UA. Else the `Q_PROPERTY` will be created in the C++ instance but not exposed through OPC UA.\n\nTo add **methods** to a subtype, the `Q_INVOKABLE` macro can be used. The limitations are than only [up to 10 arguments can used](https://doc.qt.io/qt-5/qmetamethod.html#invoke) and the argument types can only be the same supported by the `setDataType()` method (see the *Basics* section).\n\n```c++\nclass TemperatureSensor : public QUaBaseObject\n{\n\tQ_OBJECT\n\t\n\t// properties, variables, objects ...\n\npublic:\n\tQ_INVOKABLE explicit TemperatureSensor(QUaServer *server);\n\n\t// properties, variables, objects ...\n\n\tQ_INVOKABLE void turnOn();\n\tQ_INVOKABLE void turnOff();\n};\n```\n\nThe implementation is like a normal C++ class method:\n\n```c++\nvoid TemperatureSensor::turnOn()\n{\n\tstatus()-\u003esetValue(\"On\");\n}\n\nvoid TemperatureSensor::turnOff()\n{\n\tstatus()-\u003esetValue(\"Off\");\n}\n```\n\nIf the `Q_INVOKABLE` macro is not used, then the method is simply not exposed through OPC UA.\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"./res/img/04_types_03.jpg\"\u003e\n\u003c/p\u003e\n\nOne final perk of creating subtypes is the possiblity of creating custom enumerators which can be used as data types for variables. This is done using the `Q_ENUM` macro:\n\n```c++\nclass TemperatureSensor : public QUaBaseObject\n{\n\tQ_OBJECT\n\t\n\t// properties, variables, objects ...\n\npublic:\n\tQ_INVOKABLE explicit TemperatureSensor(QUaServer *server);\n\n\t// properties, variables, objects ...\n\t// methods ...\n\n\tenum Units\n\t{\n\t\tC = 0,\n\t\tF = 1\n\t};\n\tQ_ENUM(Units)\n\n};\n```\n\nThen using the enumerator to set the value of a *Variable*:\n\n```c++\nTemperatureSensor::TemperatureSensor(QUaServer *server)\n\t: QUaBaseObject(server)\n{\n\t// set defaults ...\n\t// use enum as type\n\tunits()-\u003esetDataTypeEnum(QMetaEnum::fromType\u003cTemperatureSensor::Units\u003e());\n\tunits()-\u003esetValue(Units::C);\n}\n```\n\nThen any client has knowledge of the enum options.\n\n### Types Example\n\nBuild and test the methods example in [./examples/04_types](./examples/04_types/main.cpp) to learn more.\n\n---\n\n## Server\n\nThe `QUaServer` class constructor not only allows to set a custom port to run the server (see the *Basics* section), but also to set an SSL **certificate** so that clients can **validate** the server. The `QUaServer` instance also contains methods that allow to customise the server **description** published through OPC UA.\n\nSee the [validation document](./VALIDATION.md) for more details on how validation works.\n\n### Create Certificates\n\nMake sure `openssl` is installed and follow the next commands to create the certificate (on Windows use [MSys2](https://www.msys2.org/), on Linux just use the command line).\n\nThe first step is to create a Certificate Authority (CA). The CA will take the role of a system integrator comissioned with installing OPC Servers in a plant. The CA will have to:\n\n* Create its own *public* and *private* key pair.\n\n* Create its own **self-signed** *certificate*.\n\n* Create its own *Certificate Revocation List (CRL)*.\n\nKeys can be created and transformed into various formats. Ultimately, most OPC UA applications make use of the [DER format](https://wiki.openssl.org/index.php/DER). \n\n```bash\n# Create directory to store CA's files\nmkdir ca\n# Create CA key\nopenssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out ca/ca.key\n# Create self-signed CA cert\nopenssl req -new -x509 -days 3600 -key ca/ca.key -subj \"/CN=juangburgos CA/O=juangburgos Organization\" -out ca/ca.crt\n# Convert cert to der format\nopenssl x509 -in ca/ca.crt -inform pem -out ca/ca.crt.der -outform der\n# Create cert revocation list CRL file\n# NOTE : might need to create in relative path\n#        - File './demoCA/index.txt' (Empty)\n#        - File './demoCA/crlnumber' with contents '1000'\nopenssl ca -crldays 3600 -keyfile ca/ca.key -cert ca/ca.crt -gencrl -out ca/ca.crl\n# Convert CRL to der format\nopenssl crl -in ca/ca.crl -inform pem -out ca/ca.der.crl -outform der\n```\n\nThe next steps must be applied for each server the system integrator wants to install.\n\n* Create its own *public* and *private* key pair.\n\n* Create an `exts.txt` which contain the *certificate extensions* required by the OPC UA standard.\n\n* Create its own **unsigned** certificate, and with it a *certificate sign request*.\n\n* Give the *certificate sign request* to the CA to sign it.\n\nThe `exts.txt` should be as follows:\n\n```\n[v3_ca]\nsubjectAltName=DNS:localhost,DNS:ppic09,IP:127.0.0.1,IP:192.168.1.18,URI:urn:unconfigured:application\nbasicConstraints=CA:TRUE\nsubjectKeyIdentifier=hash\nauthorityKeyIdentifier=keyid,issuer\nkeyUsage=digitalSignature,keyEncipherment\nextendedKeyUsage=serverAuth,clientAuth,codeSigning\n```\n\nThe `subjectAltName` must contains all the URLs that will be used to connect to the server. In the example above, clients might connect to the localhost (`127.0.0.1`) or through the Windows network, using the Windows PC name (`ppic09`), or through the local network (`192.168.1.18`).\n\n```bash\n# Create directory to store server's files\nmkdir server\n# Create server key\nopenssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out server/server.key\n# Convert server key to der format\nopenssl rsa -in server/server.key -inform pem -out server/server.key.der -outform der\n# Create server cert sign request\nopenssl req -new -sha256 \\\n-key server/server.key \\\n-subj \"/C=ES/ST=MAD/O=MyServer/CN=localhost\" \\\n-out server/server.csr\n```\n\nThe CA must now sign the server's *certificate sign request* to create the *signed certificate*, appending also the required *certificate extensions* (`exts.txt`).\n\n```bash\n# Sign cert sign request (NOTE: must provide exts.txt)\nopenssl x509 -days 3600 -req \\\n-in server/server.csr \\\n-extensions v3_ca \\\n-extfile server/exts.txt \\\n-CAcreateserial -CA ca/ca.crt -CAkey ca/ca.key \\\n-out server/server.crt\n# Convert cert to der format\nopenssl x509 -in server/server.crt -inform pem -out server/server.crt.der -outform der\n```\n\n### Use Certificates\n\nFirst the CA's certificate and CRL must be copied to the client's software. \n\nIn the case of *UA Expert*, in the user interface go to `Settings -\u003e Manage Certificates...`. Then click the `Open Certificate Location`, which opens the file epxlorer to a location similar to:\n\n```\n$SOME_PATH/unifiedautomation/uaexpert/PKI/trusted/certs\n```\n\nThe CA's certificate must be copied to this path:\n\n```bash\ncp ca/ca.crt.der $SOME_PATH/unifiedautomation/uaexpert/PKI/trusted/certs/ca.crt.der\n```\n\nGoing one directory up, then in `crl` is where the CRL must be copied to:\n\n```bash\ncp ca/ca.der.crl $SOME_PATH/unifiedautomation/uaexpert/PKI/trusted/crl/ca.der.crl\n```\n\nNow the *server certificate* must be copied next to the *QUaServer* application:\n\n```bash\ncp server/server.crt.der $SERVER_PATH/server.crt.der\n```\n\nAnd in the C++ code the server's certificate contents need to be passed to the `setCertificate` method **before** starting the server:\n\n```c++\n#include \u003cQCoreApplication\u003e\n#include \u003cQDebug\u003e\n#include \u003cQFile\u003e\n\n#include \u003cQUaServer\u003e\n\nint main(int argc, char *argv[])\n{\n\tQCoreApplication a(argc, argv);\n\n\tQUaServer server;\n\n\t// Load server certificate\n\tQFile certServer;\n\tcertServer.setFileName(\"server.crt.der\");\n\tQ_ASSERT(certServer.exists());\n\tcertServer.open(QIODevice::ReadOnly);\n\tserver.setCertificate(certServer.readAll());\n\tcertServer.close();\n\n\tserver.start();\n\n\treturn a.exec(); \n}\n```\n\nNow the client is able to validate the server before connecting to it.\n\nNote that eventhough validation required creating and managing cryptographic keys, the communications are yet **not encrypted**. The files generated in this section are used in the *Encryption* section to actually encrypt communications.\n\n### Server Description\n\nThe `QUaServer` instance also contains methods to add custom server description:\n\n```c++\n// Add server description\nserver.setApplicationName (\"my_app\");\nserver.setApplicationUri  (\"urn:juangburgos:my_app\");\nserver.setProductName     (\"my_product\");\nserver.setProductUri      (\"juangburgos.com\");\nserver.setManufacturerName(\"My Company Inc.\");\nserver.setSoftwareVersion (\"6.6.6-master\");\nserver.setBuildNumber     (\"gvfsed43fs\");\n```\n\nThis methods should be called **before** starting the server, else the changes won't be visible until the server is restarted.\n\nThis information is then made available to the clients through the *Server Object* that can be found by browsing to `/Root/Objects/Server/ServerStatus/BuildInfo`\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"./res/img/05_server_01.jpg\"\u003e\n\u003c/p\u003e\n\n### Server Example\n\nBuild and test the server example in [./examples/05_server](./examples/05_server/main.cpp) to learn more.\n\nSome test certificates are included for convenience in [./examples/05_server/ca_files](./examples/05_server/ca_files). **Do not use them in production**, just for testing purposes.\n\n---\n\n## Users\n\nBy default, `QUaServer` instances allow *anonymous* login. To disable it use the `setAnonymousLoginAllowed()` method as follows:\n\n```c++\nserver.setAnonymousLoginAllowed(false);\n```\n\nBut now it is necessary to create at least one user account to access the server. This can be done using the `addUser()` method:\n\n```c++\nserver.addUser(\"juan\", \"pass123\");\nserver.addUser(\"john\", \"qwerty\");\n```\n\nThe first argument is the **username** and the second is the **password**.\n\nNow when trying to connect to the server application without credentials, an error might appear in the client's log:\n\n```\nError 'BadIdentityTokenInvalid' was returned during ActivateSession\n```\n\nTo connect to the server it is necessary now to provide credentials. For example, with the *UA Expert* client right click the server and select `Properties ...`:\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"./res/img/06_users_01.jpg\"\u003e\n\u003c/p\u003e\n\nThen in *Authentication Settings* select *Username Password* and introduce the username, and click `OK`:\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"./res/img/06_users_02.jpg\"\u003e\n\u003c/p\u003e\n\nNow when connecting, the password will be requested. It is likely that the client will issue a warning:\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"./res/img/06_users_03.jpg\"\u003e\n\u003c/p\u003e\n\nThe reason is that communications are **not yet encrypted**, therefore the usermame and password will be sent in **plain text**. So any application that monitors the network (such as [Wireshark](https://www.wireshark.org/)) can read such messages and read the login credentials.\n\nFor the moment ignore the warning by clicking `Ignore` to connect to the server. In the *Encryption* section it is detailed how to encrypt communications such that the login credentials are protected from [eavesdropping](https://en.wikipedia.org/wiki/Eavesdropping) applications.\n\nHaving a list of login credentials does not only limit access to the server, but is possible to limit access to individual resources in a **per-user** basis using the `setUserAccessLevelCallback()` available in the `QUaNode` API:\n\n```c++\n// Create some varibles\nQUaFolderObject * objsFolder = server.objectsFolder();\n// NOTE : the variables need to be overall writable\n//        user-level access is defined later\nauto var1 = objsFolder-\u003eaddProperty(\"var1\");\nvar1-\u003esetWriteAccess(true);\nvar1-\u003esetValue(123);\nauto var2 = objsFolder-\u003eaddProperty(\"var2\");\nvar2-\u003esetWriteAccess(true);\nvar2-\u003esetValue(1.23);\n\n// Give access control to individual variables\nvar1-\u003esetUserAccessLevelCallback([](const QString \u0026strUserName) {\n\tQUaAccessLevel access;\n\t// Read Access to all\n\taccess.bits.bRead = true;\n\t// Write Access only to juan\n\tif (strUserName.compare(\"juan\", Qt::CaseSensitive) == 0)\n\t{\n\t\taccess.bits.bWrite = true;\n\t}\n\telse\n\t{\n\t\taccess.bits.bWrite = false;\n\t}\n\treturn access;\n});\n\nvar2-\u003esetUserAccessLevelCallback([](const QString \u0026strUserName) {\n\tQUaAccessLevel access;\n\t// Read Access to all\n\taccess.bits.bRead = true;\n\t// Write Access only to john\n\tif (strUserName.compare(\"john\", Qt::CaseSensitive) == 0)\n\t{\n\t\taccess.bits.bWrite = true;\n\t}\n\telse\n\t{\n\t\taccess.bits.bWrite = false;\n\t}\n\treturn access;\n});\n```\n\nNote the example above uses *C++ Lambdas*, but traditional callbacks can be used.\n\nNow if *John* tries to write `var1`, he might get a client log error like:\n\n```\nWrite to node 'NS0|Numeric|762789430' failed [ret = BadUserAccessDenied]\n```\n\nThe user-level access control is implemeted in a **cascading** fashion, meaning that if a variable does not have an specific *UserAccessLevelCallback* defined, then it looks if the parent has one and so on. If no node has a *UserAccessLevelCallback* defined then all access is granted. For example:\n\n```c++\nauto * obj = objsFolder-\u003eaddBaseObject(\"obj\");\n\nauto * subobj = obj-\u003eaddBaseObject(\"subobj\");\n\nauto subsubvar = subobj-\u003eaddProperty(\"subsubvar\");\nsubsubvar-\u003esetWriteAccess(true);\nsubsubvar-\u003esetValue(\"hola\");\n\n// Define access on top level object, \n// since no specific access is defined on 'subsubvar',\n// it inherits the grandparent's\nobj-\u003esetUserAccessLevelCallback([](const QString \u0026strUserName){\n\tQUaAccessLevel access;\n\t// Read Access to all\n\taccess.bits.bRead = true;\n\t// Write Access only to juan\n\tif (strUserName.compare(\"juan\", Qt::CaseSensitive) == 0)\n\t{\n\t\taccess.bits.bWrite = true;\n\t}\n\telse\n\t{\n\t\taccess.bits.bWrite = false;\n\t}\n\treturn access;\n});\n```\n\nWhen creating *custom types* it is possible to define a *default* custom access level by *reimplementing* the `userAccessLevel()` virtual method. For example:\n\nIn `customvar.h`:\n\n```c++\n#include \u003cQUaBaseDataVariable\u003e\n#include \u003cQUaProperty\u003e\n\nclass CustomVar : public QUaBaseDataVariable\n{\n\tQ_OBJECT\n\n\tQ_PROPERTY(QUaProperty         * myProp READ myProp)\n\tQ_PROPERTY(QUaBaseDataVariable * varFoo READ varFoo)\n\tQ_PROPERTY(QUaBaseDataVariable * varBar READ varBar)\n\npublic:\n\tQ_INVOKABLE explicit CustomVar(QUaServer *server);\n\n\tQUaProperty         * myProp();\n\tQUaBaseDataVariable * varFoo();\n\tQUaBaseDataVariable * varBar();\n\n\t// Reimplement virtual method to define default user access\n\t// for all instances of this type\n\tQUaAccessLevel userAccessLevel(const QString \u0026strUserName) override;\n};\n```\n\nIn `customvar.cpp`:\n\n```c++\n#include \"customvar.h\"\n\nCustomVar::CustomVar(QUaServer *server)\n\t: QUaBaseDataVariable(server)\n{\n\tthis-\u003emyProp()-\u003esetValue(\"xxx\");\n\tthis-\u003evarFoo()-\u003esetValue(true);\n\tthis-\u003evarBar()-\u003esetValue(69);\n\tthis-\u003emyProp()-\u003esetWriteAccess(true);\n\tthis-\u003evarFoo()-\u003esetWriteAccess(true);\n\tthis-\u003evarBar()-\u003esetWriteAccess(true);\n}\n\nQUaProperty * CustomVar::myProp()\n{\n\treturn this-\u003efindChild\u003cQUaProperty*\u003e(\"myProp\");\t\n}\n\nQUaBaseDataVariable * CustomVar::varFoo()\n{\n\treturn this-\u003efindChild\u003cQUaBaseDataVariable*\u003e(\"varFoo\");\n}\n\nQUaBaseDataVariable * CustomVar::varBar()\n{\n\treturn this-\u003efindChild\u003cQUaBaseDataVariable*\u003e(\"varBar\");\n}\n\nQUaAccessLevel CustomVar::userAccessLevel(const QString \u0026 strUserName)\n{\n\tQUaAccessLevel access;\n\t// Read Access to all\n\taccess.bits.bRead = true;\n\t// Write Access only to john\n\tif (strUserName.compare(\"john\", Qt::CaseSensitive) == 0)\n\t{\n\t\taccess.bits.bWrite = true;\n\t}\n\telse\n\t{\n\t\taccess.bits.bWrite = false;\n\t}\n\treturn access;\n}\n```\n\nSo any instance of `CustomVar` will use the reimplemented method by default, *unless* there exists a more **specific** callback:\n\n```c++\nQUaAccessLevel juanCanWrite(const QString \u0026strUserName) \n{\n\tQUaAccessLevel access;\n\t// Read Access to all\n\taccess.bits.bRead = true;\n\t// Write Access only to juan\n\tif (strUserName.compare(\"juan\", Qt::CaseSensitive) == 0)\n\t{\n\t\taccess.bits.bWrite = true;\n\t}\n\telse\n\t{\n\t\taccess.bits.bWrite = false;\n\t}\n\treturn access;\n}\n\n// ...\n\nauto custom1 = objsFolder-\u003eaddChild\u003cCustomVar\u003e(\"custom1\");\nauto custom2 = objsFolder-\u003eaddChild\u003cCustomVar\u003e(\"custom2\");\n\n// Set specific callbacks\ncustom1-\u003evarFoo()-\u003esetUserAccessLevelCallback(\u0026juanCanWrite);\ncustom2-\u003esetUserAccessLevelCallback(\u0026juanCanWrite);\n```\n\nIn the example above, all children variables (`myProp`, `varFoo` and `varBar`) inherit the reimplemented access level defined in the `CustomVar` class, which allows only *john* to write. But, the `varFoo` child of the `custom1` instance has an specific callback that will overrule the parent's permission.\n\nThe instance `custom2` also inherits by default the reimplemented access level defined in the `CustomVar` class, but the specific callback overrules the inherited permission.\n\n### Users Example\n\nBuild and test the server example in [./examples/06_users](./examples/06_users/main.cpp) to learn more.\n\n---\n\n## Encryption\n\nIn this section the *QUaServer* library is configured to encrypt communications. Before continuing, make sure to go through the *Server* section in detail and generate all the required certificates and keys.\n\nTo support encryption, it is necessary to add the [mbedtls library](https://github.com/ARMmbed/mbedtls) to the project's dependencies. A copy of a compatible version of the `mbedtls` library is included in this repo as a *git cubmodule* in [`./depends/mbedtls.git`](./depends/mbedtls.git).\n\nThe `mbedtls` library is built automatically when compiling the [amalgamation project](./src/amalgamation), by passing the `ua_encryption` option as follows:\n\n\n```bash\ncd ./src/amalgamation\n# Windows\nqmake \"CONFIG+=ua_encryption\" -tp vc amalgamation.pro\nmsbuild open62541.vcxproj\n# Linux\nqmake \"CONFIG+=ua_encryption\" amalgamation.pro\nmake all\n```\n\nTo compile the examples, run `qmake` again over your project to load the new configuration. For example, to update the examples in this repo run:\n\n```bash\n# Windows\nqmake \"CONFIG+=ua_encryption\" -r -tp vc examples.pro\nmsbuild examples.sln\n# Linux\nqmake \"CONFIG+=ua_encryption\" -r examples.pro\nmake all\n```\n\nAfter running `qmake` it is often necessary to **rebuild** the complete project to avoid *missing symbols* errors.\n\nNow copy the server's **certificate** and **private key** to the path where your binary is (`server.crt.der` and `server.key.der` created in the *Server* section).\n\nFinally load the *certificate* and *private key* in the C++ code and pass them to the `QUaServer` constructor:\n\n```c++\n#include \u003cQCoreApplication\u003e\n#include \u003cQDebug\u003e\n#include \u003cQFile\u003e\n\n#include \u003cQUaServer\u003e\n\nint main(int argc, char *argv[])\n{\n\tQCoreApplication a(argc, argv);\n\n\t// Load server certificate\n\tQFile certServer;\n\tcertServer.setFileName(\"server.crt.der\");\n\tQ_ASSERT(certServer.exists());\n\tcertServer.open(QIODevice::ReadOnly);\n\n\t// Load server private key\n\tQFile privServer;\n\tprivServer.setFileName(\"server.key.der\");\n\tQ_ASSERT(privServer.exists());\n\tprivServer.open(QIODevice::ReadOnly);\n\n\t// Instantiate server by passing certificate and key\n\tQUaServer server;\n\tserver.setCertificate(certServer.readAll());\n\tserver.setPrivateKey (privServer.readAll());\n\n\tcertServer.close();\n\tprivServer.close();\n\n\tserver.start();\n\n\treturn a.exec(); \n}\n```\n\nNow when the client browses for the server, there should be a new option to connect using **Sign \u0026 Encrypt** which encrypts the communications between clients and the server.\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"./res/img/07_encryption_02.jpg\"\u003e\n\u003c/p\u003e\n\n### Encryption Example\n\nBuild and test the encryption example in [./examples/07_encryption](./examples/07_encryption/main.cpp) to learn more.\n\n---\n\n## Events\n\nTo use events, it is necessary to create a new amalgamation from the *open62541* source code that supports events. This can be done by building it with the following commands:\n\n```bash\ncd ./depends/open62541.git\nmkdir build; cd build\n# Adjust your Cmake generator accordingly\ncmake -DUA_ENABLE_AMALGAMATION=ON -DUA_NAMESPACE_ZERO=FULL -DUA_ENABLE_SUBSCRIPTIONS_EVENTS=ON .. -G \"Visual Studio 15 2017 Win64\"\n```\n\n* The `-DUA_NAMESPACE_ZERO=FULL` option is needed because by default *open62541* does not include the complete address space of the OPC UA standard in order to reduce binary size. But to support events, it is actually necessary to have the `FULL` address space available in the server application.\n\n* The `-DUA_ENABLE_SUBSCRIPTIONS_EVENTS=ON` is the flag that enables events.\n\nNote that the amalgamation files are now considerably larger because now they contain the full default OPC UA address space.\n\nNow build the library using the Qt project included in this repo:\n\n```bash\ncd ./src/amalgamation\n# Windows\nqmake \"CONFIG+=ua_events\" -tp vc amalgamation.pro\nmsbuild open62541.vcxproj\n# Linux\nqmake \"CONFIG+=ua_events\" amalgamation.pro\nmake all\n```\n\nTo update the examples to support events:\n\n```bash\n# Windows\nqmake \"CONFIG+=ua_events\" -r -tp vc examples.pro\nmsbuild examples.sln\n# Linux\nqmake \"CONFIG+=ua_events\" -r examples.pro\nmake all\n```\n\nAfter running `qmake` it is often necessary to **rebuild** the application to avoid *missing symbols* errors.\n\nThe building process above is similar than the one described in the encryption section. To enable both events and encryption the we have to add both options to *QMake*:\n\n```bash\n# Windows\nqmake \"CONFIG+=ua_encryption ua_events\" -r -tp vc examples.pro\n# Linux\nqmake \"CONFIG+=ua_encryption ua_events\" -r examples.pro\n```\n\nEvents now can be used in the C++ code. To create an event, first is necessary to **subtype** the `QUaBaseEvent` class, for example:\n\nIn `myevent.h`:\n\n```c++\n#include \u003cQUaBaseEvent\u003e\n\nclass MyEvent : public QUaBaseEvent\n{\n    Q_OBJECT\n\npublic:\n\tQ_INVOKABLE explicit MyEvent(QUaServer *server);\n\n};\n```\n\nIn `myevent.cpp`:\n\n```c++\n#include \"myevent.h\"\n\nMyEvent::MyEvent(QUaServer *server)\n\t: QUaBaseEvent(server)\n{\n\n}\n```\n\nThe same rules apply as when subtyping *Objects* or *Variables* (see the *Types* section).\n\nEvents must have an **originator** node, which can be any object in the address space that allows to subscribe to events. This is defined in the [`EventNotifier`](https://reference.opcfoundation.org/v104/Core/docs/Part3/8.59/) attribute which can be accesed through the `QUaBaseObject` API:\n\n```c++\nquint8 eventNotifier() const;\nvoid setEventNotifier(const quint8 \u0026eventNotifier);\n```\n\nThe value should be an enumeration, but to simplify the usage, there are a couple of helper methods:\n\n```c++\nbool subscribeToEvents() const;\nvoid setSubscribeToEvents(const bool\u0026 subscribeToEvents);\n```\n\nThe `setSubscribeToEvents(true)` enables events for the object while `setSubscribeToEvents(false)` disables them. By default events are **disabled** for all objects. Except for the [**Server Object**](https://reference.opcfoundation.org/v104/Core/docs/Part5/8.3.2/).\n\nIf there is an event which does not originate from any object, then is necessary to use the *Server Object* to create and trigger the event. An event is instantiated using the `createEvent\u003cT\u003e()` method:\n\n```c++\nauto event = server.createEvent\u003cMyEvent\u003e();\n```\n\nNote that for events there is no need to define a `BrowseName` upon instantiation, that is because events are not normally exposed in the address space (with the exception of Alarms and Conditions).\n\nOnce an event is created, some [event variables](https://reference.opcfoundation.org/v104/Core/ObjectTypes/BaseEventType/) can be set to define the event information. This is provided by the inherited `QUaBaseEvent` API:\n\n```c++\nQString sourceName() const;\nvoid setSourceName(const QString \u0026strSourceName);\n\nQDateTime time() const;\nvoid setTime(const QDateTime \u0026dateTime);\n\nQString message() const;\nvoid setMessage(const QString \u0026strMessage);\n\nquint16 severity() const;\nvoid setSeverity(const quint16 \u0026intSeverity);\n```\n\n* `SourceName` : Description of the source of the Event.\n\n* `Time` : Time (in UTC) the Event occurred. It comes from the underlying system or device.\n\n* `Message` : Human-readable description of the Event.\n\n* `Severity` : Urgency of the Event. Value from 1 to 1000, with 1 being the lowest severity and 1000 being the highest.\n\nThe variables must be set before triggering the event. Then, the event can be triggered with the `trigger()` method.\n\nIn order to be able to test the events though, it is necessary to have a mechanism to trigger events on demand. One option is to create a method to trigger the event:\n\n```c++\nauto event = server.createEvent\u003cMyEvent\u003e();\n\nobjsFolder-\u003eaddMethod(\"triggerServerEvent\", [\u0026event]() {\n\t// set event information\n\tevent-\u003esetSourceName(\"Server\");\n\tevent-\u003esetMessage(\"An event occured in the server\");\n\tevent-\u003esetTime(QDateTime::currentDateTimeUtc());\n\tevent-\u003esetSeverity(100);\n\t// trigger event\n\tevent-\u003etrigger();\t\n});\n```\n\nIn order to visualize events, some clients require a special events window. For example in *UA Expert*, click the `Add Document` button, then select `Event View` and click `Add`. Then *drag and drop* the *Server Object* (`/Root/Objects/Server`) to the *Configuration* window. Now is possible to see events.\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"./res/img/08_events_01.jpg\"\u003e\n\u003c/p\u003e\n\nThe event can be triggered any number of times, and its variables can be updated to new values at any point. Once is not needed anymore, the event can be deleted:\n\n```c++\ndelete event;\n```\n\nIf it is desired to trigger events with an specific object as originator, simply create the event using that object's `createEvent\u003cT\u003e()` method:\n\n```c++\nQUaFolderObject * objsFolder = server.objectsFolder();\nauto obj = objsFolder-\u003eaddBaseObject(\"obj\");\n\n// Enable object for events\nobj-\u003esetSubscribeToEvents(true);\n// Create event with object as originator\nauto obj_event = obj-\u003ecreateEvent\u003cMyEvent\u003e();\n```\n\nBut now on the client it is necessary to *drag and drop* the originator object to the *Configuration* window.\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"./res/img/08_events_02.jpg\"\u003e\n\u003c/p\u003e\n\n### Events Example\n\nBuild and test the events example in [./examples/08_events](./examples/08_events/main.cpp) to learn more.\n\n---\n\n## Serialization\n\nThe *QUaServer* supports creating and destroying nodes at *runtime*. Therefore it is possible for a client to modify the server's *Address Space* remotely. This can be achieved, for example, through methods:\n\n```c++\nQUaFolderObject * objsFolder = server.objectsFolder();\n\nobjsFolder-\u003eaddMethod(\"CreateVariable\", [objsFolder](QString strVariableName) {\n\tif (objsFolder-\u003ebrowseChild(strVariableName))\n\t{\n\t\treturn QString(\"Error : Variable %1 already exists.\").arg(strVariableName);\n\t}\n\tauto newVar = objsFolder-\u003eaddBaseDataVariable(strVariableName);\n\treturn QString(\"Success : Variable %1 created.\").arg(strVariableName);\n});\n\nobjsFolder-\u003eaddMethod(\"DestroyVariable\", [objsFolder](QString strVariableName) {\n\tauto var = objsFolder-\u003ebrowseChild(strVariableName);\n\tif (!var)\n\t{\n\t\treturn QString(\"Error : Variable %1 does not exists.\").arg(strVariableName);\n\t}\n\tdelete var;\n\treturn QString(\"Success : Variable %1 destroyed.\").arg(strVariableName);\n});\n```\n\nNote it is possible to make *UaExpert* refresh the *Address Space* automatically, by compiling the snippet above with `qmake \"CONFIG+=ua_events\"`.\n\nAll the nodes created at *runtime* exist only in memory, so if the server program is restarted, all those nodes will be lost.\n\nTo solve this issue, `QUaNode` provides a *serialization* API to help saving the current state of the *Address Space* to disk, and also to be able to restore it from disk.\n\nTo save to disk, `QUaNode` provides the `serialize` method:\n\n```c++\ntemplate\u003ctypename T\u003e\nbool serialize(T\u0026 serializer, QQueue\u003cQUaLog\u003e \u0026logOut);\n```\n\nWhere `T` is any C++ *type* implementing the following interface:\n\n```c++\n// required API for QUaNode::serialize\nbool writeInstance(\n\tconst QUaNodeId\u0026 nodeId,\n\tconst QString\u0026 typeName,\n\tconst QMap\u003cQString, QVariant\u003e\u0026 attrs,\n\tconst QList\u003cQUaForwardReference\u003e\u0026 forwardRefs,\n\tQQueue\u003cQUaLog\u003e\u0026 logOut\n);\n```\n\nWhen the `serialize` method is called over a node instance, it will call the `serializer`'s `writeInstance` method recursivelly, starting with the node instance that called it and all its descendants. If the `writeInstance` method returns `false`, the recursion stops, and the call to `serialize` also returns `false`. Any useful log messages should be added to the `logOut` queue.\n\nIt is then responsability of the `writeInstance` implementation to save to disk the node's information (`nodeId`, `attrs` and `forwardRefs`) in a sensible manner. For example, in the [./examples/09_serialization](./examples/09_serialization) example, the `QUaXmlSerializer` class implements serialization to XML in the following format:\n\n```xml\n\u003c?xml version='1.0' encoding='UTF-8'?\u003e\n\u003cnodes\u003e\n \u003cn nodeId=\"ns=0;i=85\" browseName=\"Objects\" description=\"\" eventNotifier=\"0\" displayName=\"Objects\" writeMask=\"0\"\u003e\n  \u003cr forwardName=\"Organizes\" targetType=\"QUaBaseObject\" targetNodeId=\"ns=1;s=my_obj\" inverseName=\"OrganizedBy\"/\u003e\n  \u003cr forwardName=\"Organizes\" targetType=\"QUaFolderObject\" targetNodeId=\"ns=0;i=1592406929\" inverseName=\"OrganizedBy\"/\u003e\n  \u003cr forwardName=\"Organizes\" targetType=\"QUaBaseDataVariable\" targetNodeId=\"ns=0;i=2501818547\" inverseName=\"OrganizedBy\"/\u003e\n  \u003cr forwardName=\"Organizes\" targetType=\"QUaProperty\" targetNodeId=\"ns=1;s=my_prop\" inverseName=\"OrganizedBy\"/\u003e\n \u003c/n\u003e\n \u003cn nodeId=\"ns=1;s=my_obj\" browseName=\"my_object\" description=\"\" eventNotifier=\"0\" displayName=\"my_object\" writeMask=\"0\"\u003e\n  \u003cr forwardName=\"FriendOf\" targetType=\"TemperatureSensor\" targetNodeId=\"ns=0;i=2070436686\" inverseName=\"FriendOf\"/\u003e\n  \u003cr forwardName=\"HasProperty\" targetType=\"QUaProperty\" targetNodeId=\"ns=0;i=2687773104\" inverseName=\"PropertyOf\"/\u003e\n  \u003cr forwardName=\"HasOrderedComponent\" targetType=\"QUaBaseObject\" targetNodeId=\"ns=0;i=4261035154\" inverseName=\"OrderedComponentOf\"/\u003e\n  \u003cr forwardName=\"HasOrderedComponent\" targetType=\"QUaFolderObject\" targetNodeId=\"ns=0;i=2452012465\" inverseName=\"OrderedComponentOf\"/\u003e\n \u003c/n\u003e\n \u003c!-- more nodes ... --\u003e\n\u003c/nodes\u003e\n```\n\nIt simply writes down a list of nodes, each node with the `\u003cn\u003e` *XML tag* and all the node's attributes as *XML attributes*. Each `\u003cn\u003e` contains a list of `\u003cr\u003e` *XML tags* as children listing the *forward references* for that node. This is all the information required to *serialize* the state of the *Address Space*.\n\nNote that the `typeName` is not serialized as an *XML attribute*. The `typeName` is passed to the `writeInstance` method to allow the user to organize the data by type if desired. In the *XML* serialization this is not necessary, but if serializing to *SQL*, knowing the `typeName` might be useful to store all instance of a type in their own table.\n\nThe type `T` can *optionally* implement the following interface:\n\n```c++\n// optional API for QUaNode::serialize\nbool serializeStart(QQueue\u003cQUaLog\u003e\u0026 logOut);\n\n// optional API for QUaNode::serialize\nbool serializeEnd(QQueue\u003cQUaLog\u003e\u0026 logOut);\n```\n\nImplementing such methods can be useful to perform intialization tasks such as opening a file for writing, and to perform clean up tasks such as closing the file or release any other resources.\n\nTo serialize all node instances in the *Address Space*, `serialize` should be called over the *Objects Folder* of the server, for example:\n\n```c++\nobjsFolder-\u003eaddMethod(\"Serialize\", [objsFolder](QString strFileName) {\n\tQUaXmlSerializer serializer;\n\tQQueue\u003cQUaLog\u003e logOut;\n\tif (!serializer.setXmlFileName(strFileName, logOut))\n\t{\n\t\treturn QString(\"Error in file name.\");\n\t}\n\tif (!objsFolder-\u003eserialize(serializer, logOut))\n\t{\n\t\treturn QString(\"Error serializing. Check the logOut.\");\n\t}\n\treturn QString(\"Success : Serialized to %1 file.\").arg(strFileName);\n});\n```\n\nTo restoring the *Address Space* from disk, `QUaNode` provides the `deserialize` method:\n\n```c++\ntemplate\u003ctypename T\u003e\nbool deserialize(T\u0026 deserializer, QQueue\u003cQUaLog\u003e\u0026 logOut);\n```\n\nWhere `T` is any C++ *type* implementing the following interface:\n\n```c++\n// required API for QUaNode::deserialize\nbool readInstance(\n\tconst QUaNodeId \u0026nodeId,\n\tconst QString \u0026typeName,\n\tQMap\u003cQString, QVariant\u003e \u0026attrs,\n\tQList\u003cQUaForwardReference\u003e \u0026forwardRefs,\n\tQQueue\u003cQUaLog\u003e \u0026logOut\n);\n\n// optional API for QUaNode::deserialize\nbool deserializeStart(QQueue\u003cQUaLog\u003e\u0026 logOut);\n\n// optional API for QUaNode::deserialize\nbool deserializeEnd(QQueue\u003cQUaLog\u003e\u0026 logOut);\n```\n\nWhich work in a similar fashion as the serializing interface (`writeInstance`, `serializeStart` and `serializeEnd` respectively).\n\nWhen deserializing, it is responsability of the `readInstance` implementation to return the node's information (`typeName`, `attrs` and `forwardRefs`) for a given `nodeId` and `typeName`.\n\nThe `deserialize` call then takes care of restoring the *Address Space* instatiating any missing nodes or overwriting the attributes of any existing nodes.\n\nTo deserialize all node instances in the *Address Space*, `deserialize` should be called over the *Objects Folder* of the server, for example:\n\n```c++\nobjsFolder-\u003eaddMethod(\"Deserialize\", [objsFolder](QString strFileName) {\n\tQUaXmlSerializer serializer;\n\tQQueue\u003cQUaLog\u003e logOut;\n\tif (!serializer.setXmlFileName(strFileName, logOut))\n\t{\n\t\treturn QString(\"Error in file name.\");\n\t}\n\tif (!objsFolder-\u003edeserialize(serializer, logOut))\n\t{\n\t\treturn QString(\"Error deserializing. Check the logOut.\");\n\t}\n\treturn QString(\"Success : Deserialized from %1 file.\").arg(strFileName);\n});\n```\n\nNote that the `readInstance` interface requires the underlying data source to be **queryable** by the `nodeId`. This is not the case for an XML file, therefore the `QUaXmlSerializer` example loads all the contents of the XML into a *queryable* structure in memory. As the number of nodes scale, loading all the contents from disk to memory might not be feasible. Then serializing to a *queryable* database might be a better alternative. In the [./examples/09_serialization](./examples/09_serialization) example, the `QUaSqliteSerializer` class implements serialization to a *queryable* *Sqlite* database.\n\nBoth `QUaXmlSerializer` and `QUaSqliteSerializer` classes provided in the [./examples/09_serialization](./examples/09_serialization) example are just to demonstrate the use if the serialization API. They are by no means the best or most efficient way to serialize the *Address Space*, the user should provide their own *serializer* implementation.\n\n### Serialization Example\n\nBuild and test the events example in [./examples/09_serialization](./examples/09_serialization/main.cpp) to learn more.\n\n---\n\n## Historizing\n\nThe *QUaServer* supports storing *histrical data* and *historical events*, exposing them through the [*HistoryRead* service](https://reference.opcfoundation.org/v104/Core/docs/Part4/5.10.3/).\n\nTo enable this functionality, build the library using the Qt project included in this repo using the `ua_historizing` configuration flag:\n\n```bash\ncd ./src/amalgamation\n# Windows\nqmake \"CONFIG+=ua_historizing\" -tp vc amalgamation.pro\nmsbuild open62541.vcxproj\n# Linux\nqmake \"CONFIG+=ua_historizing\" amalgamation.pro\nmake all\n```\n\nTo update the examples to support *historizing*:\n\n```bash\n# Windows\nqmake \"CONFIG+=ua_historizing\" -r -tp vc examples.pro\nmsbuild examples.sln\n# Linux\nqmake \"CONFIG+=ua_historizing\" -r examples.pro\nmake all\n```\n\nTo support *historizing*, `QUaServer` provides the `setHistorizer` method:\n\n```c++\ntemplate\u003ctypename T\u003e\nbool setHistorizer(T\u0026 historizer);\n```\n\n### Historizing Data\n\nTo historize data, the historizer `T` can be any C++ *type* implementing the following interface:\n\n```c++\n// required API for QUaServer::setHistorizer\n// write data point to backend, return true on success\nbool writeHistoryData(\n\tconst QUaNodeId \u0026nodeId,\n\tconst QUaHistoryDataPoint \u0026dataPoint,\n\tQQueue\u003cQUaLog\u003e  \u0026logOut\n);\n// required API for QUaServer::setHistorizer\n// update an existing node's data point in backend, return true on success\nbool updateHistoryData(\n\tconst QUaNodeId \u0026nodeId, \n\tconst QUaHistoryDataPoint \u0026dataPoint,\n\tQQueue\u003cQUaLog\u003e  \u0026logOut\n);\n// required API for QUaServer::setHistorizer\n// remove an existing node's data points within a range, return true on success\nbool removeHistoryData(\n\tcconst QUaNodeId \u0026nodeId, \n\tconst QDateTime  \u0026timeStart,\n\tconst QDateTime  \u0026timeEnd,\n\tQQueue\u003cQUaLog\u003e   \u0026logOut\n); \n// required API for QUaServer::setHistorizer\n// return the timestamp of the first sample available for the given node\nQDateTime firstTimestamp(\n\tconst QString  \u0026strNodeId,\n\tQQueue\u003cQUaLog\u003e \u0026logOut\n) const;\n// required API for QUaServer::setHistorizer\n// return the timestamp of the latest sample available for the given node\nQDateTime lastTimestamp(\n\tconst QUaNodeId \u0026nodeId, \n\tQQueue\u003cQUaLog\u003e  \u0026logOut\n) const;\n// required API for QUaServer::setHistorizer\n// return true if given timestamp is available for the given node\nbool hasTimestamp(\n\tconst QString   \u0026strNodeId,\n\tconst QDateTime \u0026timestamp,\n\tQQueue\u003cQUaLog\u003e  \u0026logOut\n) const;\n// required API for QUaServer::setHistorizer\n// return a timestamp matching the criteria for the given node\nQDateTime findTimestamp(\n\tconst QUaNodeId \u0026nodeId, \n\tconst QDateTime \u0026timestamp,\n\tconst QUaHistoryBackend::TimeMatch\u0026 match,\n\tQQueue\u003cQUaLog\u003e  \u0026logOut\n) const;\n// required API for QUaServer::setHistorizer\n// return the number for data points within a time range for the given node\nquint64 numDataPointsInRange(\n\tconst QUaNodeId \u0026nodeId, \n\tconst QDateTime \u0026timeStart,\n\tconst QDateTime \u0026timeEnd,\n\tQQueue\u003cQUaLog\u003e  \u0026logOut\n) const;\n// required API for QUaServer::setHistorizer\n// return the numPointsToRead data points for the given node\n// starting from the numPointsOffset offset after given start time (pagination)\nQVector\u003cQUaHistoryDataPoint\u003e readHistoryData(\n\tconst QUaNodeId \u0026nodeId, \n\tconst QDateTime \u0026timeStart,\n\tconst quint64   \u0026numPointsOffset,\n\tconst quint64   \u0026numPointsToRead,\n\tQQueue\u003cQUaLog\u003e  \u0026logOut\n) const;\n```\n\nTo allow **storing** data it is only necessary to implement `writeHistoryData`, while the other methods can return default values. The data will be saved to whatever media it is desired. \n\nImplementing only `writeHistoryData`, means clients won't be able to access the historcal data remotely yet, for that it is necessary to implement more methods of the API, as explained further below.\n\nTo *store* the data, the API passes the *NodeId* (`const QUaNodeId \u0026nodeId`) of the variable to be historized.\n\nThe `QUaHistoryDataPoint` structure (`const QUaHistoryDataPoint \u0026dataPoint`) provides the information that needs to be stored:\n\n```c++\nstruct QUaHistoryDataPoint\n{\n\tQDateTime timestamp;\n\tQVariant  value;\n\tquint32   status;\n};\n```\n\nWhatever storage media is chosen, it must be *queriable* first, by *NodeId* and second, by *Timestamp*. \n\nFor example, if a `SQL` database is chosen for storage, one approach is to create one table for each *NodeId*. Each table having three columns for *time*, *value* and *status* respectively. To speed up queries, it is recommended to create *indexes* over the *time* column.\n\nSome *pseudo-SQL* code is used in this documentation to illustrate *possible* implementation of each API method. For example, to create each *NodeId* table:\n\n```sql\nCREATE TABLE \":NodeId\" (\n\t[:NodeId] INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,\n\t[Time] INTEGER NOT NULL,\n\t[Value] :DataType NOT NULL,\n\t[Status] INTEGER NOT NULL\n);\n-- create index to optimize queries by time\nCREATE UNIQUE INDEX \":NodeId_Time\" ON \":NodeId\"(Time);\n```\n\nFor `writeHistoryData`:\n\n```sql\nINSERT INTO \":NodeId\" (Time, Value, Status) VALUES (:Time, :Value, :Status);\n```\n\nAll the methods of the API should populate the `QQueue\u003cQUaLog\u003e \u0026logOut` parameter with log entries describing any error occurred during the storing or querying process.\n\nTo allow an OPC UA Client to **access** the historcal data remotely, it is necessary to further implement the `firstTimestamp`, `lastTimestamp`, `hasTimestamp`, `findTimestamp`, `numDataPointsInRange` and `readHistoryData`. The implementation of this methods is self-describing by their names. Below some *pseudo-SQL* to illustrate *possible* implementation:\n\nFor `firstTimestamp`:\n\n```sql\nSELECT p.Time FROM \":NodeId\" p ORDER BY p.Time ASC LIMIT 1;\n```\n\nFor `lastTimestamp`:\n\n```sql\nSELECT p.Time FROM \":NodeId\" p ORDER BY p.Time DESC LIMIT 1;\n```\n\nFor `hasTimestamp`:\n\n```sql\nSELECT COUNT(*) FROM \":NodeId\" p WHERE p.Time = :Time;\n```\n\nFor `findTimestamp`:\n\n```sql\n-- from above\nSELECT p.Time FROM \":NodeId\" p WHERE p.Time \u003e :Time ORDER BY p.Time ASC LIMIT 1;\n-- from below\nSELECT p.Time FROM \":NodeId\" p WHERE p.Time \u003c :Time ORDER BY p.Time DESC LIMIT 1;\n```\n\nFor `numDataPointsInRange`:\n\n```sql\nSELECT COUNT(*) FROM \":NodeId\" p WHERE p.Time \u003e= :TimeStart AND p.Time \u003c= :TimeEnd ORDER BY p.Time ASC;\n```\n\nFor `readHistoryData`:\n\n```sql\nSELECT p.Time, p.Value, p.Status FROM\":NodeId\" p WHERE p.Time \u003e= :Time ORDER BY p.Time ASC LIMIT :Limit OFFSET :Offset;\n```\n\nTo allow *modifying* historical data, the `updateHistoryData` and `removeHistoryData` should be implemented accordingly.\n\nFinally, to historize a variable, the `QUaBaseVariable::setHistorizing(const bool\u0026 historizing)` method should be called. And to allow clients to access its historical data remotelly, the `QUaBaseVariable::setReadHistoryAccess(const bool\u0026 readHistoryAccess)` method should be called. For example:\n\n```c++\n// create int variable\nauto varInt = objsFolder-\u003eaddBaseDataVariable(\"MyInt\", \"ns=0;s=MyInt\");\nvarInt-\u003esetValue(0);\n// NOTE : must enable historizing for each variable\nvarInt-\u003esetHistorizing(true);\nvarInt-\u003esetReadHistoryAccess(true);\n```\n\nSimilarly, to allow clients to modify the historical data, the `QUaBaseVariable::setWriteHistoryAccess(const bool\u0026 bHistoryWrite)` method should be called.\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"./res/img/10_historizing_01_data.gif\"\u003e\n\u003c/p\u003e\n\n### Historizing Events\n\nHistorizing events is only possible if the `QUaServer` project is compiled using the `CONFIG+=ua_events` flag. See the *Events* section of this document for more information.\n\nTo historize events, the historizer `T` can be any C++ *type* implementing the following interface:\n\n```c++\n// write a event's data to backend\nbool writeHistoryEventsOfType(\n\tconst QUaNodeId            \u0026eventTypeNodeId,\n\tconst QList\u003cQUaNodeId\u003e     \u0026emittersNodeIds,\n\tconst QUaHistoryEventPoint \u0026eventPoint,\n\tQQueue\u003cQUaLog\u003e             \u0026logOut\n);\n// get event types (node ids) for which there are events stored for the given emitter\nQVector\u003cQUaNodeId\u003e eventTypesOfEmitter(\n\tconst QUaNodeId \u0026emitterNodeId,\n\tQQueue\u003cQUaLog\u003e  \u0026logOut\n);\n// find a timestamp matching the criteria for the emitter and event type\nQDateTime findTimestampEventOfType(\n\tconst QUaNodeId                    \u0026emitterNodeId,\n\tconst QUaNodeId                    \u0026eventTypeNodeId,\n\tconst QDateTime                    \u0026timestamp,\n\tconst QUaHistoryBackend::TimeMatch \u0026match,\n\tQQueue\u003cQUaLog\u003e                     \u0026logOut\n);\n// get the number for events within a time range for the given emitter and event type\nquint64 numEventsOfTypeInRange(\n\tconst QUaNodeId \u0026emitterNodeId,\n\tconst QUaNodeId \u0026eventTypeNodeId,\n\tconst QDateTime \u0026timeStart,\n\tconst QDateTime \u0026timeEnd,\n\tQQueue\u003cQUaLog\u003e  \u0026logOut\n);\n// return the numPointsToRead events for the given emitter and event type,\n// starting from the numPointsOffset offset after given start time (pagination)\nQVector\u003cQUaHistoryEventPoint\u003e readHistoryEventsOfType(\n\tconst QUaNodeId \u0026emitterNodeId,\n\tconst QUaNodeId \u0026eventTypeNodeId,\n\tconst QDateTime \u0026timeStart,\n\tconst quint64   \u0026numPointsOffset,\n\tconst quint64   \u0026numPointsToRead,\n\tconst QList\u003cQUaBrowsePath\u003e \u0026columnsToRead,\n\tQQueue\u003cQUaLog\u003e  \u0026logOut\n);\n```\n\nTo allow **storing** events it is only necessary to implement `writeHistoryEventsOfType`, while the other methods can return default values. The events will be saved to whatever media it is desired. \n\nImplementing only `writeHistoryEventsOfType`, means clients won't be able to access the historcal events remotely yet, for that it is necessary to implement more methods of the API, as explained further below.\n\nTo *store* the events, the API passes the *NodeId* (`const QUaNodeId \u0026eventTypeNodeId`) of the **event type** to be historized, a list of *emitter* nodeIds and the event data.\n\nThe `QUaHistoryEventPoint` structure provides the information that needs to be stored:\n\n```c++\nstruct QUaHistoryEventPoint\n{\n\tQDateTime timestamp;\n\tQHash\u003cQUaBrowsePath, QVariant\u003e fields;\n};\n```\n\nHistorizing events is slightly more complicated than historizing data. Mainly because *historical data* has always the same struture (`{timestamp, value, status}`), while *event data* changes depending on the *event type*, and there can be any number of event types, including the custom ones. \n\nAnother complication is that in OPC UA, the same event can be *emitted* or *notified* by different objects (objects that are not necessarily be the event's `SourceNode`) according to a [*Event Refereneces*](https://reference.opcfoundation.org/v104/Core/docs/Part3/7.18/) organization defined by the OPC specification. So when the event history is queried for a *notifier* node, all the events that were emitted by this node must be retrieved. This relation is specified by the `const QList\u003cQUaNodeId\u003e \u0026emittersNodeIds` argument in the `writeHistoryEventsOfType` historic API method.\n\nWhatever storage media is chosen, events must be *queriable* first by *EventType*, then by *Emitter*, and finally by *Timestamp*. \n\nFor example, if a `SQL` database is chosen for storage, one approach is to create one table for each *EventType*. Each table having a fixed number of columns according to the fixed amout of event fields an *EventType* has. The `const QUaHistoryEventPoint \u0026eventPoint` argument if the `writeHistoryEventsOfType` API method is always guaranteed to contain the same event fields for a given type. So the `QUaHistoryEventPoint::fields` information can be used to create the *EventType* tables.\n\n```sql\nCREATE TABLE \":EventTypeNodeId\" ( :EventFieldNames :EventFieldTypes );\n```\n\nThen create one able to store the *EventType* table names, to be able to relate them to a unique index.\n\n```sql\nCREATE TABLE \"EventTypeTableNames\"\n(\n\t[EventTypeTableNames] INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,\n\t[TableName] TEXT NOT NULL\n);\n```\n\nTo speed up queries, it is recommended to create an *index* over the *TableName* column.\n\nThen a table per *Emitter* can be created with fixed a number of columns that help query and relate to the events stored in the *EventType* tables.\n\n```sql\nCREATE TABLE \":EmitterNodeId\"\n(\n\t[:EmitterNodeId] INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,\n\t[Time] INTEGER NOT NULL,      -- to be able to query emitter's events by time range\n\t[EventType] INTEGER NOT NULL, -- index of EventTypeTableNames table\n\t[EventId] INTEGER NOT NULL    -- index of event in its EventType table\n);\n```\n\nTo speed up queries, it is recommended to create an *index* over the *Time* and *EventType* columns.\n\n```sql\nCREATE INDEX \":EmitterNodeId_Time_EventType\" ON \":EmitterNodeId\" (Time, EventType);\n```\n\nThen the procedure to store an event when the `writeHistoryEventsOfType` API method is called would be:\n\n* Insert new event in its *EventType* table, return new event's *EventType* key.\n\n* Check if the (*EventType*) *TableName* already in the *EventTypeTableNames* table else insert it, fetch the *EventTypeTableNames* key.\n\n* Insert the key of the new event and event type in each emitter table.\n\nThen the rest of the API to query the event history could be imlpemented as follows:\n\nFor `eventTypesOfEmitter`:\n\n```sql\nSELECT n.TableName FROM EventTypeTableNames n \nINNER JOIN \n(\n\tSELECT DISTINCT EventType FROM \":EmitterNodeId\"\n) e\nON n.EventTypeTableNames = e.EventType\n```\n\nFor `findTimestampEventOfType`:\n\n```sql\n-- from above\nSELECT e.Time FROM \":EmitterNodeId\" e WHERE e.Time \u003e= :Time AND e.EventType = :EventTypeKey ORDER BY e.Time ASC LIMIT 1;\n-- from below\nSELECT e.Time FROM \":EmitterNodeId\" e WHERE e.Time \u003c :Time AND e.EventType = :EventTypeKey ORDER BY e.Time DESC LIMIT 1;\n```\n\nFor `numEventsOfTypeInRange`:\n\n```sql\nSELECT COUNT(*) FROM \":EmitterNodeId\" e WHERE e.Time \u003e= :TimeStart AND e.Time \u003c= :TimeEnd AND e.EventType = :EventTypeKey ORDER BY e.Time ASC;\n```\n\nFor `readHistoryEventsOfType` :\n\n```sql\nSELECT * FROM \":EventTypeNodeId\" t \nINNER JOIN \n(\n\tSELECT EventId FROM \":EmitterNodeId\" e WHERE e.Time \u003e= :TimeStart AND e.EventType = :EventTypeKey ORDER BY e.Time ASC LIMIT :Limit OFFSET :Offset\n) e\nON t.EventTypeNodeId = e.EventId;\n```\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"./res/img/10_historizing_02_events.gif\"\u003e\n\u003c/p\u003e\n\n### Historizing Example\n\nThe [`quainmemoryhistorizer.cpp`](./examples/10_historizing/quainmemoryhistorizer.cpp) file shows an example of historical data and event storage in memory, while the [`quasqlitehistorizer.cpp`](./examples/10_historizing/quasqlitehistorizer.cpp) file shows an example of historical storage using *Sqlite*.\n\nNote that these examples are provided for illustration purposes only and not for production. The user is encouraged to implement (and if possible, share) their own historizer implementations.\n\nBuild and test the historizing example in [./examples/10_historizing](./examples/10_historizing/main.cpp) to learn more.\n\n---\n\n## Alarms\n\nAt the time of writing, alarms and conditions are considered an `EXPERIMENTAL` feature in the *open62541* library, therefore the same applies for *QUaServer*. Please use with caution.\n\nTo use alarms and conditions, it is necessary to create a new amalgamation from the *open62541* source code that supports alarms. This can be done by building it with the following commands:\n\n```bash\ncd ./depends/open62541.git\nmkdir build; cd build\n# Adjust your Cmake generator accordingly\ncmake -DUA_ENABLE_AMALGAMATION=ON -DUA_NAMESPACE_ZERO=FULL -DUA_ENABLE_SUBSCRIPTIONS_EVENTS=ON -DUA_ENABLE_SUBSCRIPTIONS_ALARMS_CONDITIONS=ON .. -G \"Visual Studio 15 2017 Win64\"\n```\n\n* The `-DUA_NAMESPACE_ZERO=FULL` option is needed because by default *open62541* does not include the complete address space of the OPC UA standard in order to reduce binary size. But to support events, it is actually necessary to have the `FULL` address space available in the server application.\n\n* The `-DUA_ENABLE_SUBSCRIPTIONS_EVENTS=ON` is the flag that enables events.\n\n* The `-DUA_ENABLE_SUBSCRIPTIONS_ALARMS_CONDITIONS=ON` is the flag that enables alarms and conditions.\n\nNote that the amalgamation files are now considerably larger because now they contain the full default OPC UA address space.\n\nNow build the library using the Qt project included in this repo:\n\n```bash\ncd ./src/amalgamation\n# Windows\nqmake \"CONFIG+=ua_alarms_conditions\" -tp vc amalgamation.pro\nmsbuild open62541.vcxproj\n# Linux\nqmake \"CONFIG+=ua_alarms_conditions\" amalgamation.pro\nmake all\n```\n\nTo update the examples to support events:\n\n```bash\n# Windows\nqmake \"CONFIG+=ua_alarms_conditions\" -r -tp vc examples.pro\nmsbuild examples.sln\n# Linux\nqmake \"CONFIG+=ua_alarms_conditions\" -r examples.pro\nmake all\n```\n\nAfter running `qmake` it is often necessary to **rebuild** the application to avoid *missing symbols* errors.\n\nTwo types of alarms are available out of the box by the `QUaServer` API:\n\n* `QUaOffNormalAlarm` : Used for alarms based on discrete values. Useful not only for alarms based on *boolean* values, but also any other discrete values such as *integers*.\n\n* `QUaExclusiveLevelAlarm` : Used for alarms based in continuous numeric values. It provides automatic level checking.\n\n### QUaOffNormalAlarm\n\nTo create a `QUaOffNormalAlarm`, the first step is to create an object that will be the `SourceNode` of the events triggered by the alarm. Clients will be then able to subscribe to events emitted by this object in order to track the alarm state.\n\n```c++\nauto motionSensor = objsFolder-\u003eaddChild\u003cQUaBaseObject\u003e(\"motionSensor\");\n```\n\nThen a variable is needed that will provide the discrete value that the alarm will monitor. Any variable that contains a discrete value can be used.\n\n```c++\nauto moving = motionSensor-\u003eaddBaseDataVariable(\"moving\");\nmoving-\u003esetWriteAccess(true);\nmoving-\u003esetDataType(QMetaType::Bool);\nmoving-\u003esetValue(false);\n```\n\nFinally the `QUaOffNormalAlarm` can be created based on its `SourceNode`, setting the variable with the discrete value as an `InputNode` and defining what the *Normal Value* of the `InputNode` should be.\n\n```c++\nauto motionAlarm = motionSensor-\u003eaddChild\u003cQUaOffNormalAlarm\u003e(\"alarm\");\nmotionAlarm-\u003esetConditionName(\"Motion Sensor Alarm\");\nmotionAlarm-\u003esetInputNode(moving);\nmotionAlarm-\u003esetNormalValue(false);\nmotionAlarm-\u003esetConfirmRequired(true);\n```\n\nFor the alarm to start generating events, first it has to be **enabled**. This can be done by calling the `Enable` method of the alarm object through the network using an OPC client or programmatically using the C++ `Enable()` method.\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"./res/img/11_alarms_01_offnormal.gif\"\u003e\n\u003c/p\u003e\n\n### QUaExclusiveLevelAlarm\n\nTo create a `QUaExclusiveLevelAlarm`, the first step is to create an object that will be the `SourceNode` of the events triggered by the alarm. Clients will be then able to subscribe to events emitted by this object in order to track the alarm state.\n\n```c++\nauto levelSensor = objsFolder-\u003eaddChild\u003cQUaBaseObject\u003e(\"levelSensor\");\n```\n\nThen a variable is needed that will provide the continuous value that the alarm will monitor. Any variable that contains a continuous value can be used.\n\n```c++\nauto level = levelSensor-\u003eaddBaseDataVariable(\"level\");\nlevel-\u003esetWriteAccess(true);\nlevel-\u003esetDataType(QMetaType::Double);\nlevel-\u003esetValue(0.0);\n```\n\nThen the `QUaExclusiveLevelAlarm` can be created based on its `SourceNode`, setting the variable with the continuous value as an `InputNode`. \n\n```c++\nauto levelAlarm = levelSensor-\u003eaddChild\u003cQUaExclusiveLevelAlarm\u003e(\"alarm\");\nlevelAlarm-\u003esetConditionName(\"Level Sensor Alarm\");\nlevelAlarm-\u003esetInputNode(level);\n\nlevelAlarm-\u003esetHighLimitRequired(true);\nlevelAlarm-\u003esetLowLimitRequired(true);\nlevelAlarm-\u003esetHighLimit(10.0);\nlevelAlarm-\u003esetLowLimit(-10.0);\n```\n\nBy default the `QUaExclusiveLevelAlarm` does not monitor any limits, so they have to be required explicitly using the `QUaExclusiveLevelAlarm` API:\n\n```c++\n// to enabled the monitoring of specific limits\nvoid setHighHighLimitRequired(const bool\u0026 highHighLimitRequired);\nvoid setHighLimitRequired    (const bool\u0026 highLimitRequired    );\nvoid setLowLimitRequired     (const bool\u0026 lowLimitRequired     );\nvoid setLowLowLimitRequired  (const bool\u0026 lowLowLimitRequired  );\n\n// to define the limits\ndouble highHighLimit() const;\nvoid setHighHighLimit(const double\u0026 highHighLimit);\n\ndouble highLimit() const;\nvoid setHighLimit(const double\u0026 highLimit);\n\ndouble lowLimit() const;\nvoid setLowLimit(const double\u0026 lowLimit);\n\ndouble lowLowLimit() const;\nvoid setLowLowLimit(const double\u0026 lowLowLimit);\n```\n\nFor the alarm to start generating events, first it has to be **enabled**. This can be done by calling the `Enable` method of the alarm object through the network using an OPC client or programmatically using the C++ `Enable()` method.\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"./res/img/11_alarms_02_level.gif\"\u003e\n\u003c/p\u003e\n\n### Branches\n\nSupport for [branches](https://reference.opcfoundation.org/v104/Core/docs/Part9/5.5.3/) in `QUaServer` is disabled by default. To enable branches call the `setBranchQueueSize` method with a value larger than `0`. This will create a branch queue in the alarm which will keep the given number of branches in memory. If more branches are created than the size of the queue, the oldest branch will be deleted automatically to avoid memory saturation.\n\n```c++\nmotionAlarm-\u003esetBranchQueueSize(10);\nlevelAlarm-\u003esetBranchQueueSize(10);\n```\n\nHistorizing of branches is also disabled by default, to enable it, call the `setHistorizingBranches` method with a `true` value.\n\n```c++\nmotionAlarm-\u003esetHistorizingBranches(true);\nlevelAlarm-\u003esetHistorizingBranches(true);\n```\n\n---\n\n## License\n\n### Amalgamation\n\nThe amalgamation source code found in `./src/amalgamation` is licensed by **open62541** under the [Mozilla Public License 2.0](https://github.com/open62541/open62541/blob/master/LICENSE).\n\n### QUaTypesConverter\n\nThe source code in the files `./src/wrapper/quatypesconverter.h` and `quatypesconverter.cpp` was copied and adapted from the [QtOpcUa repository](https://github.com/qt/qtopcua) (files [qopen62541valueconverter.h](https://github.com/qt/qtopcua/blob/5.12/src/plugins/opcua/open62541/qopen62541valueconverter.h) and [qopen62541valueconverter.cpp](https://github.com/qt/qtopcua/blob/5.12/src/plugins/opcua/open62541/qopen62541valueconverter.cpp)) and is under the [LGPL license](https://github.com/qt/qtopcua/blob/5.12/LICENSE.LGPLv3).\n\n### QUaServer\n\nFor the rest of the code, the license is [MIT](https://opensource.org/licenses/MIT).\n\nCopyright (c) 2019 -2020 Juan Gonzalez Burgos\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FQUaServer%2FQUaServer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FQUaServer%2FQUaServer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FQUaServer%2FQUaServer/lists"}