水ポンプ
OPC UAサーバーと連動し、シンプルなウォーターポンプ機用のQMLベースのHMIを構築。
Water Pump の例では、Qt OPC UA を使用して OPC UA サーバーと対話し、シンプルな機械の QML ベースの HMI を構築する方法を示しています。
サーバーの構築
Water Pump の例を使用する前に、Water Pump Simulation Server をビルドする必要があります。QtCreator で開くか、通常通りターミナルからビルドしてください。
シミュレーション
このサンプルに含まれるOPC UAサーバーは、2つのタンク、水ポンプ、バルブを含む機械のシミュレーションを実行します。水は1つ目のタンクから2つ目のタンクに汲み上げられ、バルブを開くことで2つ目のタンクから流されます。どちらの操作にも、ユーザーが設定可能な設定値があり、第2のタンクに汲み上げる水量や、第2のタンクから流す水量を制御することができます。
サーバーには以下のノードが存在する:
NodeId | 機能 |
---|---|
ns=2;s=Machine | マシンのメソッドノードと変数ノードを含むフォルダ |
ns=2;s=Machine.State | マシンの状態 |
ns=2;s=Machine.Tank1.PercentFilled | 最初のタンクの現在の充填状態 |
ns=2;s=Machine.Tank2.PercentFilled(充填率 | 2つ目のタンクの現在の充填状態 |
ns=2;s=Machine.Tank2.TargetPercent | ポンピングとフラッシングの設定値 |
ns=2;s=Machine.Tank2.ValveState | 第2タンクのバルブの状態 |
ns=2;s=機械.指定 | 表示用に人間が読み取れる機械の名称 |
ns=2;s=Machine.Start | このメソッドを呼び出してポンプを始動させる |
ns=2;s=Machine.Stop | ポンプを停止するには、このメソッドを呼び出します。 |
ns=2;s=Machine.FlushTank2 | このメソッドをコールして、タンク 2 を洗浄する |
ns=2;s=Machine.Reset | このメソッドを呼び出してシミュレーションをリセットする |
すべてのメソッドは、成功した場合はGood を返し、操作が不正な場合はBadUserAccessDenied を返します(たとえば、最初のタンクが空のときにポンプを起動しようとした場合など)。
クライアントの機能
この例では、読み取り、書き込み、メソッド呼び出し、データ変更サブスクリプションを使用し、QOpcUaClient とQOpcUaNode で提供される非同期操作のハンドラをセットアップする方法を示します。
実装
バックエンドクラスは、OPC UA サーバーとの通信を処理し、OPC UA メソッドコールをラップするプロパティとQ_INVOKABLE
メソッドによって、このサーバーのコンテンツを公開するために使用されます。
メンバー変数
接続管理にはQOpcUaClient へのポインタが必要です。HMIが相互作用するOPC UAノードごとに、QOpcUaNode オブジェクトへの追加のポインタが必要です。これらのノードの値には、サーバーから最後に報告された値を含むメンバ変数が追加されます。
... QScopedPointer<QOpcUaClient> m_client; QScopedPointer<QOpcUaNode> m_machineStateNode; QScopedPointer<QOpcUaNode> m_percentFilledTank1Node; QScopedPointer<QOpcUaNode> m_percentFilledTank2Node; QScopedPointer<QOpcUaNode> m_tank2TargetPercentNode; QScopedPointer<QOpcUaNode> m_tank2ValveStateNode; QScopedPointer<QOpcUaNode> m_machineNode; QScopedPointer<QOpcUaNode> m_machineDesignationNode; double m_percentFilledTank1; double m_percentFilledTank2; double m_tank2TargetPercent; bool m_tank2ValveState; MachineState m_machineState; QString m_machineDesignation; ...
HMIで使用される各値に対して、ゲッター、変更シグナル、プロパティが追加され、QMLでのプロパティバインディングが可能になります。
... Q_PROPERTY(double percentFilledTank1 READ percentFilledTank1 NOTIFY percentFilledTank1Changed) Q_PROPERTY(double percentFilledTank2 READ percentFilledTank2 NOTIFY percentFilledTank2Changed) Q_PROPERTY(double tank2TargetPercent READ tank2TargetPercent NOTIFY tank2TargetPercentChanged) Q_PROPERTY(OpcUaMachineBackend::MachineState machineState READ machineState NOTIFY machineStateChanged) Q_PROPERTY(bool tank2ValveState READ tank2ValveState NOTIFY tank2ValveStateChanged) Q_PROPERTY(bool connected READ connected NOTIFY connectedChanged) Q_PROPERTY(QString machineDesignation READ machineDesignation NOTIFY machineDesignationChanged) Q_PROPERTY(QString message READ message NOTIFY messageChanged) ...
非同期ハンドラ
Qt OPC UA の非同期APIでは、すべての操作にシグナルハンドラが必要です。
データ変更サブスクリプションはQOpcUaNode::attributeUpdated を使用してそれらの更新を報告する。このシグナルに接続されたハンドラは、新しい値をQVariant として取得し、その値を変数に書き込んだり、新しい値を持つシグナルを発信したりすることができます。
void OpcUaMachineBackend::percentFilledTank1Updated(QOpcUa::NodeAttribute attr, const QVariant &value) { Q_UNUSED(attr); m_percentFilledTank1 = value.toDouble(); emit percentFilledTank1Changed(m_percentFilledTank1); }
読み取り操作が完了すると、QOpcUaNode::attributeRead シグナルが発信される。クライアントはステータス・コードをチェックし、ノードから結果を取得する必要があります。
void OpcUaMachineBackend::machineDesignationRead(QOpcUa::NodeAttributes attr) { if (attr & QOpcUa::NodeAttribute::Value) { // Make sure the value attribute has been read if (m_machineDesignationNode->attributeError(QOpcUa::NodeAttribute::Value) == QOpcUa::UaStatusCode::Good) { // Make sure there was no error m_machineDesignation = m_machineDesignationNode->attribute(QOpcUa::NodeAttribute::Value).toString(); // Get the attribute from the cache emit machineDesignationChanged(m_machineDesignation); } } }
サーバーとのやりとり
コンストラクタでは、QOpcUaProvider が作成され、バックエンド選択ドロップダウンメニューのモデルを提供するために利用可能なバックエンドが保存されます。
... QOpcUaProvider provider; setBackends(provider.availableBackends()); ...
接続を試みる前に、選択したバックエンドを持つQOpcUaClient が作成されます。そのQOpcUaClient::endpointsRequestFinished シグナルはバックエンドのrequestEndpointsFinished
スロットに接続されます。QOpcUaClient::stateChanged シグナルはバックエンドのclientStateHandler
スロットに接続する必要があります。
voidOpcUaMachineBackend::connectToEndpoint(constQString&url、 qint32index) {if(m_connected)return; QOpcUaProviderプロバイダ;if(index< 0 ||index>=m_backends.size())return;// 無効なインデックス if(!m_client||(m_client&& m_client->backend()!=m_backends.at(index))){ m_client.reset(provider.createClient(m_backends.at(index)));if(m_client) { { ::connect(m_client->backend() ! QObject::connect(m_client.data(), &);if (m_client) {::connect(m_client.data(), &)QOpcUaClient::endpointsRequestFinished, this, &OpcUaMachineBackend::requestEndpointsFinished); QObject::connect(m_client.data(), &QOpcUaClient::stateChanged, this, &OpcUaMachineBackend::clientStateHandler); } }if(!m_client) {もし(!m_client) qWarning() << "Could not create client"; m_successfullyCreated= false;return; } m_successfullyCreated= true; m_client->requestEndpoints(url); }
OpcUaMachineBackend::requestEndpointsFinished
スロットは、サーバー上の利用可能なエンドポイントのリストを受け取り、リストの最初のエントリへの接続を開始します。利用可能なエンドポイントがない場合、接続の確立は中断されます。
voidOpcUaMachineBackend::requestEndpointsFinished(constQList<QOpcUaEndpointDescription> &endpoints) {if(endpoints.isEmpty()) { { { (endpoints.isEmpty()) qWarning() << "The server did not return any endpoints"; clientStateHandler(QOpcUaClient::ClientState::Disconnected);return; } m_client->connectToEndpoint(endpoints.at(0)); }
clientStateHandler
は が接続または切断されたときに動作します。接続に成功した場合、先に作成したノード・メンバ変数にノード・オブジェクトが格納されます。QOpcUaClient
... if (state == QOpcUaClient::ClientState::Connected) { setMessage(u"Connected"_s); // Create node objects for reading, writing and subscriptions m_machineNode.reset(m_client->node(u"ns=2;s=Machine"_s)); m_machineStateNode.reset(m_client->node(u"ns=2;s=Machine.State"_s)); m_percentFilledTank1Node.reset(m_client->node(u"ns=2;s=Machine.Tank1.PercentFilled"_s)); m_percentFilledTank2Node.reset(m_client->node(u"ns=2;s=Machine.Tank2.PercentFilled"_s)); m_tank2TargetPercentNode.reset(m_client->node(u"ns=2;s=Machine.Tank2.TargetPercent"_s)); m_tank2ValveStateNode.reset(m_client->node(u"ns=2;s=Machine.Tank2.ValveState"_s)); m_machineDesignationNode.reset(m_client->node(u"ns=2;s=Machine.Designation"_s)); ...
すべてのノード・オブジェクトが作成された後、データ変更ハンドラがノード・オブジェクトに接続され、監視が有効になります。
... // Connect signal handlers for subscribed values QObject::connect(m_machineStateNode.data(), &QOpcUaNode::dataChangeOccurred, this, &OpcUaMachineBackend::machineStateUpdated); QObject::connect(m_percentFilledTank1Node.data(), &QOpcUaNode::dataChangeOccurred, this, &OpcUaMachineBackend::percentFilledTank1Updated); QObject::connect(m_percentFilledTank2Node.data(), &QOpcUaNode::dataChangeOccurred, this, &OpcUaMachineBackend::percentFilledTank2Updated); QObject::connect(m_tank2TargetPercentNode.data(), &QOpcUaNode::dataChangeOccurred, this, &OpcUaMachineBackend::tank2TargetPercentUpdated); QObject::connect(m_tank2ValveStateNode.data(), &QOpcUaNode::dataChangeOccurred, this, &OpcUaMachineBackend::tank2ValveStateUpdated); // Subscribe to data changes m_machineStateNode->enableMonitoring( QOpcUa::NodeAttribute::Value, QOpcUaMonitoringParameters(100)); m_percentFilledTank1Node->enableMonitoring( QOpcUa::NodeAttribute::Value, QOpcUaMonitoringParameters(100)); m_percentFilledTank2Node->enableMonitoring( QOpcUa::NodeAttribute::Value, QOpcUaMonitoringParameters(100)); m_tank2TargetPercentNode->enableMonitoring( QOpcUa::NodeAttribute::Value, QOpcUaMonitoringParameters(100)); m_tank2ValveStateNode->enableMonitoring( ...
マシンの指定は変更されないことになっており、起動時に一度読み込まれます。
... // Connect the handler for async reading QObject::connect(m_machineDesignationNode.data(), &QOpcUaNode::attributeRead, this, &OpcUaMachineBackend::machineDesignationRead); // Request the value attribute of the machine designation node m_machineDesignationNode->readAttributes(QOpcUa::NodeAttribute::Value); ...
セットポイント用のセッターがバックエンドに追加される。
void OpcUaMachineBackend::machineWriteTank2TargetPercent(double value) { if (m_tank2TargetPercentNode) m_tank2TargetPercentNode->writeAttribute(QOpcUa::NodeAttribute::Value, value); }
メソッドについては、OPC UAサーバーのメソッドを呼び出すラッパーが作成されます。
void OpcUaMachineBackend::startPump() { m_machineNode->callMethod(u"ns=2;s=Machine.Start"_s); }
HMI
バックエンドのインスタンスが作成され、uaBackend
というコンテキストプロパティとしてQMLパートに渡されます。
... OpcUaMachineBackend backend; QQmlApplicationEngine engine; engine.rootContext()->setContextProperty("uaBackend", &backend); ...
uaBackendのプロパティ、シグナル、Q_INVOKABLE
メソッドはQMLコードからアクセスできるようになります。例えば、2つ目のタンクを洗浄するボタンは、バックエンドがサーバーに接続され、マシンがアイドルで、タンクレベルが設定値を超えている場合にのみ有効になります。クリックすると、flushTank2()
メソッドがサーバー上で呼び出されます。
Button { id: flushButton text: "Flush" enabled: uaBackend.connected && uaBackend.machineState === OpcUaMachineBackend.MachineState.Idle && uaBackend.percentFilledTank2 > uaBackend.tank2TargetPercent onClicked: { uaBackend.flushTank2() } }
バックエンドからのシグナルはQMLコードの中で直接使うこともできます。
Connections { target: uaBackend function onPercentFilledTank2Changed(value) { if (uaBackend.machineState === OpcUaMachineBackend.MachineState.Pumping) rotation += 15 } }
使用方法
サーバーは HMI アプリケーションによって自動的に起動されます。Connect ボタンをクリックしてサーバーに接続した後、スライダーをドラッグしてセットポイントを設定します。その後、Start をクリックすると、1つ目のタンクから2つ目のタンクへの送水が開始されます。第二タンクの現在値より低いセットポイントを設定した場合、Flush をクリックするとバルブが開きます。
水が残っていない場合は、Reset simulation をクリックして最初のタンクに水を補給します。
ファイル
Qt Quick ウォーターポンプも参照のこと 。
© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.