aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README.md199
-rw-r--r--docs/guide-qml.md318
2 files changed, 517 insertions, 0 deletions
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..a396945
--- /dev/null
+++ b/README.md
@@ -0,0 +1,199 @@
+# BobinkQtOpcUa
+
+A C++ library that wraps [Qt OPC UA](https://doc.qt.io/qt-6/qtopcua-index.html) into simple QML components for Qt Quick applications. Hide OPC UA complexity behind a clean, declarative API.
+
+## Quick start
+
+### Prerequisites
+
+- CMake >= 3.16, Ninja
+- Qt 6.10.2+ (with `qt-cmake` in PATH)
+- OpenSSL development headers
+
+### Build
+
+```bash
+git clone --recurse-submodules https://github.com/user/BobinkQtOpcUa.git
+cd BobinkQtOpcUa
+
+qt-cmake -S . -B build -G Ninja
+qt-cmake --build build --parallel
+```
+
+The first configure automatically builds the bundled dependencies (open62541 and QtOpcUa) from git submodules under `deps/`. Subsequent builds skip this step.
+
+### Run the demo
+
+```bash
+./build/bin/BobinkDemo
+```
+
+The demo app discovers local OPC UA servers, connects with configurable authentication and security, and lets you monitor and write to nodes.
+
+## Usage
+
+Import the `Bobink` QML module:
+
+```qml
+import Bobink
+```
+
+### Connect to a server
+
+```qml
+Connections {
+ target: Bobink
+ function onConnectedChanged() {
+ console.log("Connected:", Bobink.connected)
+ }
+ function onConnectionError(message) {
+ console.log("Error:", message)
+ }
+ function onCertificateTrustRequested(certInfo) {
+ // Show certInfo to user, then call:
+ Bobink.acceptCertificate() // or rejectCertificate()
+ }
+}
+
+Button {
+ text: "Connect"
+ onClicked: {
+ Bobink.serverUrl = "opc.tcp://localhost:4840"
+ Bobink.connectToServer()
+ }
+}
+```
+
+`connectToServer()` discovers endpoints and auto-selects the most secure one. If that fails, use `connectDirect()` with explicit security settings:
+
+```qml
+Bobink.connectDirect(Bobink.Basic256Sha256, Bobink.SignAndEncrypt)
+```
+
+### Authentication
+
+```qml
+OpcUaAuth {
+ id: auth
+ mode: OpcUaAuth.Anonymous // or UserPass, Certificate
+ username: "user"
+ password: "pass"
+}
+
+// Set before connecting
+Bobink.auth = auth
+Bobink.connectToServer()
+```
+
+### Monitor and write nodes
+
+```qml
+OpcUaMonitoredNode {
+ id: tempNode
+ nodeId: "ns=2;s=Temperature"
+ monitored: true // creates an OPC UA monitored item
+ publishingInterval: 500 // ms
+
+ onValueChanged: console.log("Temperature:", value)
+ onWriteCompleted: (success, message) => console.log(message)
+}
+
+// Display live value
+Text { text: "Temperature: " + tempNode.value }
+
+// Write a new value
+Button {
+ text: "Set to 25"
+ enabled: tempNode.writable
+ onClicked: tempNode.writeValue(25)
+}
+```
+
+Type coercion is automatic -- QML values are converted to the node's OPC UA data type. For arrays, pass a comma-separated string: `writeValue("1, 2, 3")`.
+
+Write to specific array elements with index range syntax:
+
+```qml
+tempNode.writeValueAtRange("42", "0") // first element
+tempNode.writeValueAtRange("1, 2, 3", "0:2") // elements 0-2
+```
+
+### Node metadata
+
+Each `OpcUaMonitoredNode` exposes an `info` property with metadata read once on setup:
+
+```qml
+Text { text: node.info.displayName }
+Text { text: node.info.dataType }
+Text { text: node.info.description }
+// Also: nodeClass, valueRank, arrayDimensions, accessLevel,
+// status, sourceTimestamp, serverTimestamp
+```
+
+### Server discovery
+
+```qml
+Component.onCompleted: {
+ Bobink.discoveryUrl = "opc.tcp://localhost:4840"
+ Bobink.startDiscovery()
+}
+
+ListView {
+ model: Bobink.servers
+ delegate: Text {
+ text: modelData.serverName
+ // Also available: modelData.applicationUri, modelData.discoveryUrls
+ }
+}
+```
+
+### PKI
+
+Client certificates are auto-detected from the default PKI directory (`<AppDataLocation>/pki`). To configure manually:
+
+```qml
+Bobink.pkiDir = "/path/to/pki"
+Bobink.certFile = "/path/to/cert.der"
+Bobink.keyFile = "/path/to/key.pem"
+Bobink.applyPki()
+```
+
+## Use as a git submodule
+
+BobinkQtOpcUa can be added to your project as a git submodule:
+
+```bash
+git submodule add https://github.com/user/BobinkQtOpcUa.git deps/bobink
+git submodule update --init --recursive
+```
+
+```cmake
+# CMakeLists.txt
+add_subdirectory(deps/bobink)
+
+# Link against the library and its QML plugin
+target_link_libraries(YourApp PRIVATE BobinkQtOpcUaplugin)
+
+# Set RPATH so the open62541 backend plugin loads at runtime
+set(CMAKE_BUILD_RPATH "${OPEN62541_INSTALL_DIR}/lib"
+ "${QTOPCUA_INSTALL_DIR}/lib")
+
+# Tell Qt where to find the OPC UA backend plugin
+target_compile_definitions(YourApp PRIVATE
+ QTOPCUA_PLUGIN_PATH="${QTOPCUA_INSTALL_DIR}/plugins")
+```
+
+## Project structure
+
+```
+src/
+ OpcUaClient.h/.cpp Bobink singleton: connection, discovery, PKI
+ OpcUaMonitoredNode.h/.cpp Node monitoring, reading, writing
+ OpcUaAuth.h/.cpp Authentication configuration
+demo/
+ Main.qml Connection UI demo
+ NodePage.qml Node interaction demo
+deps/
+ open62541/ C OPC UA stack (submodule)
+ qtopcua/ Qt OPC UA module (submodule)
+```
diff --git a/docs/guide-qml.md b/docs/guide-qml.md
new file mode 100644
index 0000000..1bbe0f5
--- /dev/null
+++ b/docs/guide-qml.md
@@ -0,0 +1,318 @@
+# Guide QML Bobink
+
+Ce guide explique comment utiliser les composants QML fournis par la bibliothèque Bobink pour se connecter à un serveur OPC UA, lire des valeurs en temps réel et écrire des commandes -- le tout directement depuis vos fichiers QML, sans toucher au C++.
+
+Une application de démonstration complète est disponible dans le dossier `demo/` du projet. Les fichiers `Main.qml` et `NodePage.qml` illustrent toutes les fonctionnalités décrites ici. N'hésitez pas à vous en servir comme point de départ.
+
+## Importer le module
+
+En haut de chaque fichier QML où vous utilisez Bobink :
+
+```qml
+import Bobink
+```
+
+Cet import rend disponibles trois éléments :
+
+| Nom | Rôle |
+|-----|------|
+| `Bobink` | Singleton qui gère la connexion au serveur |
+| `OpcUaAuth` | Configuration de l'authentification |
+| `OpcUaMonitoredNode` | Surveillance et écriture d'un noeud OPC UA |
+
+## Connexion au serveur
+
+### 1. Configurer l'adresse
+
+```qml
+Bobink.serverUrl = "opc.tcp://192.168.1.10:4840"
+```
+
+### 2. Choisir l'authentification
+
+Créez un objet `OpcUaAuth` et assignez-le à `Bobink.auth` avant de vous connecter.
+
+**Connexion anonyme** (aucun identifiant requis) :
+
+```qml
+OpcUaAuth {
+ id: auth
+ mode: OpcUaAuth.Anonymous
+}
+```
+
+**Connexion avec identifiants** :
+
+```qml
+OpcUaAuth {
+ id: auth
+ mode: OpcUaAuth.UserPass
+ username: "operateur"
+ password: "motdepasse"
+}
+```
+
+**Connexion par certificat** :
+
+```qml
+OpcUaAuth {
+ id: auth
+ mode: OpcUaAuth.Certificate
+ certPath: "/chemin/vers/certificat.der"
+ keyPath: "/chemin/vers/cle.pem"
+}
+```
+
+Ensuite, assignez et connectez :
+
+```qml
+Bobink.auth = auth
+Bobink.connectToServer()
+```
+
+### 3. Réagir aux événements de connexion
+
+Utilisez un bloc `Connections` pour suivre l'état de la connexion :
+
+```qml
+Connections {
+ target: Bobink
+
+ function onConnectedChanged() {
+ if (Bobink.connected)
+ console.log("Connecté au serveur")
+ else
+ console.log("Déconnecté")
+ }
+
+ function onConnectionError(message) {
+ console.log("Erreur :", message)
+ }
+}
+```
+
+### 4. Certificat du serveur inconnu
+
+Si le serveur présente un certificat que le client ne connaît pas, le signal `certificateTrustRequested` est émis. Vous pouvez afficher une fenêtre de confirmation :
+
+```qml
+Connections {
+ target: Bobink
+ function onCertificateTrustRequested(certInfo) {
+ // Afficher certInfo à l'utilisateur, puis :
+ Bobink.acceptCertificate() // pour accepter
+ // ou
+ Bobink.rejectCertificate() // pour refuser
+ }
+}
+```
+
+### 5. Déconnexion
+
+```qml
+Bobink.disconnectFromServer()
+```
+
+## Surveiller un noeud (lecture en temps réel)
+
+`OpcUaMonitoredNode` est un composant non visuel (comme `Timer` ou `Connections`). Placez-le à l'intérieur de n'importe quel élément QML. Il crée un abonnement OPC UA sur le noeud et reçoit les nouvelles valeurs automatiquement.
+
+```qml
+OpcUaMonitoredNode {
+ id: temperature
+ nodeId: "ns=2;s=Temperature"
+ monitored: true
+}
+
+Text {
+ text: "Température : " + temperature.value
+}
+```
+
+### Propriétés principales
+
+| Propriété | Type | Description |
+|-----------|------|-------------|
+| `nodeId` | string | Identifiant du noeud OPC UA (ex. `"ns=2;s=Temperature"`) |
+| `monitored` | bool | Active ou désactive la surveillance. Par défaut : `true` |
+| `publishingInterval` | double | Intervalle de mise à jour en millisecondes. Par défaut : 250 |
+| `value` | variant | Valeur actuelle du noeud (lecture seule, mise à jour automatique) |
+| `writable` | bool | `true` si le noeud autorise l'écriture (lecture seule) |
+| `info` | objet | Métadonnées du noeud (voir plus bas) |
+
+### Activer/désactiver la surveillance
+
+Il est conseillé de désactiver la surveillance quand le composant n'est pas visible, pour économiser les ressources réseau. Par exemple, avec un `StackView` :
+
+```qml
+OpcUaMonitoredNode {
+ nodeId: "ns=2;s=Temperature"
+ monitored: maPage.StackView.status === StackView.Active
+}
+```
+
+Ainsi le noeud n'est surveillé que lorsque la page est affichée.
+
+### Changer l'intervalle de mise à jour
+
+```qml
+OpcUaMonitoredNode {
+ nodeId: "ns=2;s=Temperature"
+ publishingInterval: 1000 // une fois par seconde
+}
+```
+
+**Regroupement des abonnements :** côté serveur, les noeuds qui partagent le même `publishingInterval` sont regroupés dans un même abonnement (subscription) OPC UA. Moins il y a d'abonnements distincts, moins le serveur et le réseau sont sollicités. Essayez donc de donner le même intervalle à autant de noeuds que possible. Par exemple, si la plupart de vos noeuds n'ont pas besoin d'une mise à jour plus rapide que 500 ms, laissez-les tous à 500 ms plutôt que de varier les intervalles sans raison.
+
+Pour appliquer un changement d'intervalle en cours d'exécution, désactivez puis réactivez la surveillance :
+
+```qml
+temperature.monitored = false
+temperature.publishingInterval = 2000
+temperature.monitored = true
+```
+
+## Écrire une valeur
+
+### Écriture simple
+
+```qml
+OpcUaMonitoredNode {
+ id: consigne
+ nodeId: "ns=2;s=Setpoint"
+}
+
+TextField {
+ id: champValeur
+ placeholderText: "Nouvelle valeur..."
+}
+
+Button {
+ text: "Écrire"
+ enabled: consigne.writable
+ onClicked: consigne.writeValue(champValeur.text)
+}
+```
+
+La conversion de type est automatique : la valeur QML (texte, nombre, booléen) est convertie vers le type OPC UA du noeud.
+
+Pour écrire un tableau, séparez les valeurs par des virgules :
+
+```qml
+consigne.writeValue("10, 20, 30")
+```
+
+### Écriture partielle dans un tableau (index range)
+
+Pour modifier uniquement certains éléments d'un tableau sans réécrire le tout :
+
+```qml
+// Modifier le premier élément
+consigne.writeValueAtRange("42", "0")
+
+// Modifier les éléments 0 à 2
+consigne.writeValueAtRange("10, 20, 30", "0:2")
+```
+
+### Résultat de l'écriture
+
+Le signal `writeCompleted` indique si l'écriture a réussi :
+
+```qml
+OpcUaMonitoredNode {
+ id: consigne
+ nodeId: "ns=2;s=Setpoint"
+ onWriteCompleted: (success, message) => {
+ if (success)
+ console.log("Écriture réussie")
+ else
+ console.log("Échec :", message)
+ }
+}
+```
+
+## Métadonnées d'un noeud
+
+La propriété `info` donne accès aux métadonnées, lues une seule fois à la connexion :
+
+```qml
+OpcUaMonitoredNode {
+ id: noeud
+ nodeId: "ns=2;s=Temperature"
+}
+
+Column {
+ Text { text: "Nom : " + noeud.info.displayName }
+ Text { text: "Description : " + noeud.info.description }
+ Text { text: "Type : " + noeud.info.dataType }
+}
+```
+
+| Champ | Description |
+|-------|-------------|
+| `displayName` | Nom lisible du noeud |
+| `description` | Description textuelle |
+| `nodeClass` | Classe du noeud (Variable, Object, etc.) |
+| `dataType` | Type de donnée (Int32, Float, String, etc.) |
+| `valueRank` | Rang (-1 = scalaire, 1 = tableau 1D, etc.) |
+| `arrayDimensions` | Dimensions du tableau |
+| `accessLevel` | Niveau d'accès (lecture, écriture) |
+| `status` | Code de statut OPC UA |
+| `sourceTimestamp` | Horodatage côté source |
+| `serverTimestamp` | Horodatage côté serveur |
+
+## Découverte de serveurs
+
+Bobink peut interroger un serveur de découverte local (LDS) pour lister les serveurs OPC UA disponibles sur le réseau :
+
+```qml
+Component.onCompleted: {
+ Bobink.discoveryUrl = "opc.tcp://localhost:4840"
+ Bobink.startDiscovery()
+}
+
+ListView {
+ model: Bobink.servers
+ delegate: ItemDelegate {
+ required property var modelData
+ text: modelData.serverName
+ onClicked: Bobink.serverUrl = modelData.discoveryUrls[0]
+ }
+}
+```
+
+Chaque élément de `Bobink.servers` contient :
+
+- `serverName` -- nom du serveur
+- `applicationUri` -- identifiant unique de l'application
+- `discoveryUrls` -- liste des URL de connexion
+
+Appelez `Bobink.stopDiscovery()` pour arrêter la recherche (par exemple une fois connecté).
+
+## Certificats client (PKI)
+
+Au démarrage, Bobink cherche automatiquement un certificat client dans le dossier PKI par défaut. Si vous devez configurer manuellement :
+
+```qml
+Bobink.pkiDir = "/chemin/vers/pki"
+Bobink.certFile = "/chemin/vers/certificat.der"
+Bobink.keyFile = "/chemin/vers/cle.pem"
+Bobink.applyPki()
+```
+
+Les propriétés `Bobink.pkiDir`, `Bobink.certFile` et `Bobink.keyFile` sont aussi consultables pour afficher les chemins actuels dans votre interface.
+
+## Récapitulatif
+
+| Ce que vous voulez faire | Code |
+|--------------------------|------|
+| Se connecter | `Bobink.connectToServer()` |
+| Se déconnecter | `Bobink.disconnectFromServer()` |
+| Lire une valeur en temps réel | `OpcUaMonitoredNode { nodeId: "..." }` puis `monNoeud.value` |
+| Écrire une valeur | `monNoeud.writeValue(valeur)` |
+| Écrire dans un tableau partiellement | `monNoeud.writeValueAtRange(valeur, "0:2")` |
+| Vérifier si un noeud est inscriptible | `monNoeud.writable` |
+| Afficher le nom d'un noeud | `monNoeud.info.displayName` |
+| Lister les serveurs | `Bobink.startDiscovery()` puis `Bobink.servers` |
+
+Pour un exemple concret et complet, consultez les fichiers `demo/Main.qml` et `demo/NodePage.qml`.