WebCFaceの機能紹介・チュートリアルです。
このチュートリアルはプロセス間通信をメインにしたものです。 WebUIでのデータの可視化やWebUIからのプログラム操作に重点をおいたチュートリアルは 1-1. Tutorial (Visualizing)
このチュートリアルでは C++ (Meson または CMake)、または Python (Python3.6以上が必要です) を使用します。
WebCFaceのインストール
READMEにしたがって webcface, webcface-webui, webcface-tools をインストールしましょう。
C++の場合はインストールしたwebcfaceパッケージにクライアントライブラリも含まれています。
Server
WebCFaceを使用するときはserverを常時立ち上げておく必要があります。 次のように WebCFace Desktop アプリから、またはコマンドラインからの2つの方法があります。
WebCFace Desktop
スタートメニュー(Windows)、 アプリケーションメニュー(Ubuntu)、 Launchpadまたはアプリケーションフォルダ(Mac) に 「WebCFace Desktop」のアイコン が表示されているはずなので、それを起動してください。
それを起動すると、WebCFaceのメイン画面(WebUI)が起動すると同時に、バックグラウンドでサーバーが起動します。
コマンドライン
ターミナルを開いて webcface-server
コマンドを実行するとサーバーが起動します。 デフォルトでは7530番ポートを開きクライアントの接続を待ちます。 追加で指定できるコマンドラインオプションなど、詳細は 2-1. Server
Setup
ここからWebCFaceを使ったプログラムを書いていきます。
まずはプロジェクトを作成しWebCFaceライブラリを使えるようにします。
- Note
- C++でMesonやCMakeを使わない場合、pkg-configを使ったり手動でコンパイル・リンクすることも可能です。 詳細な説明は 3-1. Setup WebCFace Library
C++ (Meson) 適当にディレクトリを作ります
mkdir webcface-tutorial
cd webcface-tutorial
以下のようにWebCFaceの初期化 + 簡単な Hello World を書きます
- send.cc
#include <iostream>
int main() {
wcli.waitConnection();
std::cout << "Hello, World! (sender)" << std::endl;
}
サーバーに接続するクライアント。
Definition client.h:18
- recv.cc
#include <iostream>
int main() {
wcli.waitConnection();
std::cout << "Hello, World! (receiver)" << std::endl;
}
- Note
- Clientの初期化はグローバル変数でもローカル変数 (main() の最初) でもどちらでも構いませんが、 このチュートリアルではグローバル変数にします。
- meson.build
project('webcface-tutorial', 'cpp',
default_options: ['cpp_std=c++17'],
)
webcface_dep = dependency('webcface')
executable('tutorial-send', 'send.cc',
dependencies: webcface_dep,
)
executable('tutorial-recv', 'recv.cc',
dependencies: webcface_dep,
)
ビルドして、実行します
meson setup build # ← 初回のみ
meson compile -C build
./build/tutorial-send
ターミナルをもう1つ開いて
実行すると、Hello, World! (sender)
Hello, World! (receiver)
と出力されます。
- Note
- このチュートリアルでは以降ビルドと実行の手順は省略しますが、 同様に
meson compile
(またはninja) でビルドして ./build/tutorial-send
と ./build/tutorial-recv
を実行してください。
C++ (CMake) 適当にディレクトリを作ります
mkdir webcface-tutorial
cd webcface-tutorial
以下のようにWebCFaceの初期化 + 簡単な Hello World を書きます
- send.cc
#include <iostream>
int main() {
wcli.waitConnection();
std::cout << "Hello, World! (sender)" << std::endl;
}
- recv.cc
#include <iostream>
int main() {
wcli.waitConnection();
std::cout << "Hello, World! (receiver)" << std::endl;
}
- Note
- Clientの初期化はグローバル変数でもローカル変数 (main() の最初) でもどちらでも構いませんが、 このチュートリアルではグローバル変数にします。
- CMakeLists.txt
cmake_minimum_required(VERSION 3.5)
project(webcface-tutorial)
find_package(webcface 2.0 CONFIG REQUIRED)
add_executable(tutorial-send send.cc)
target_link_libraries(tutorial-send PRIVATE webcface::webcface)
add_executable(tutorial-recv recv.cc)
target_link_libraries(tutorial-recv PRIVATE webcface::webcface)
ビルドして、実行します
cmake -B build # ← 初回のみ
cmake --build build
./build/tutorial-send
ターミナルをもう1つ開いて
実行すると、Hello, World! (sender)
Hello, World! (receiver)
と出力されます。
- Note
- このチュートリアルでは以降ビルドと実行の手順は省略しますが、 同様に
cmake --build
(またはmakeやninjaなどでも可) でビルドして ./build/tutorial-send
と ./build/tutorial-recv
を実行してください。
Python PythonでWebCFaceを使うには、クライアントライブラリをインストールする必要があります。 venv内でもグローバルにインストールしても構いません。
以下のようにWebCFaceの初期化 + 簡単な Hello World を書きます
- send.py
from webcface import Client
wcli = Client("tutorial-send")
wcli.wait_connection()
print("Hello, World! (sender)")
- recv.py
from webcface import Client
wcli = Client("tutorial-recv")
wcli.wait_connection()
print("Hello, World! (receiver)")
- Note
- Clientの初期化はグローバル変数でもローカル変数 (main() やクラスの初期化など) でもどちらでも構いませんが、 このチュートリアルではグローバル変数にします。
実行します ターミナルをもう1つ開いて 実行すると、Hello, World! (sender)
Hello, World! (receiver)
と出力されます。
Value, Text
まずは数値や文字列のデータを送受信してみましょう。 (他のPub-Sub型通信でいうところのTopicをPublishするようなものです)
- Note
- 以降このチュートリアルではC++同士・Python同士の通信だけでなく、C++のsend側とPythonのrecv側、のように組み合わせても動作します。
コールバック
データを受信する側では、現在の値を繰り返し確認するのではなく、値が変化した時に処理が実行されるようにするという使い方もできます。 (他のPub-Sub型通信でいうところのSubscribeのようなものです)
C++
- send側は上のプログラムと同じです。
- recv.cc
#include <iostream>
#include <thread>
int main() {
wcli.waitConnection();
std::cout << "Hello, World! (receiver)" << std::endl;
std::cout <<
"data changed: " << v.
get() << std::endl;
});
std::cout <<
"message changed: " << t.
get() << std::endl;
});
while (true) {
wcli.sync();
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
Memberを指すクラス
Definition member.h:22
Text text(std::string_view field="") const
Definition field.cc:56
Value value(std::string_view field="") const
Definition field.cc:54
文字列の送受信データを表すクラス
Definition text.h:183
const std::string & get() const
文字列を返す (const参照)
Definition text.cc:99
const Text & onChange(F callback) const
値が変化したときに呼び出されるコールバックを設定
Definition text.h:251
実数値またはその配列の送受信データを表すクラス
Definition value.h:23
const Value & onChange(std::function< void(Value)> callback) const
値が変化したときに呼び出されるコールバックを設定
Definition value.cc:57
double get() const
値を返す
Definition value.h:196
Member member() const
Memberを返す
Definition field.cc:21
これを実行すると、1秒ごとに受信した値が表示される代わりに、send側からデータが送られてきたタイミングで1度だけ
data changed: 100
message changed: Hello, World! (sender)
のように表示されます。
このプログラムではsend側は常に同じデータを送っていますが、違うデータを送るようにすればデータが変わったタイミングで再度コールバックが呼ばれます。
- Note
- このサンプルプログラムではsend側がデータを送ってからrecv側のコールバックが呼ばれるまで1秒のラグがあると思います。これはrecv側がデータを受信する処理(
wcli.sync()
)が1秒に1回しか呼ばれていないためです。 これを改善するにはsync()を呼ぶ頻度を上げるか、 4-1. Client のページで説明していますがwcli.loopSync()
などが使えます。
Value, Textについては 5-1. Value, 5-2. Text に詳細なドキュメントがあります。
Python
- send側は上のプログラムと同じです。
- recv.py
from webcface import Client, Value, Text
import time
wcli = Client("tutorial-recv")
wcli.wait_connection()
print("Hello, World! (receiver)")
sender = wcli.member("tutorial-send")
@sender.value("data").on_change
def on_data_change(v: Value):
print(f"data changed: {v.get()}")
@sender.text("message").on_change
def on_message_change(t: Text):
print(f"message changed: {t}")
while True:
wcli.sync()
time.sleep(1)
- Note
@sender.value("data").on_change
はデコレータです(糖衣構文というPythonの構文です)。 この書き方に馴染みがないなら def on_data_change(v: Value):
print(f"data changed: {v.get()}")
sender.value("data").on_change(on_data_change)
と書いても同じです(がこの場合は@を使ったほうがかんたんに書けます)。
これを実行すると、1秒ごとに受信した値が表示される代わりに、send側からデータが送られてきたタイミングで1度だけ
data changed: 100
message changed: Hello, World! (sender)
のように表示されます。
このプログラムではsend側は常に同じデータを送っていますが、違うデータを送るようにすればデータが変わったタイミングで再度コールバックが呼ばれます。
- Note
- このサンプルプログラムではsend側がデータを送ってからrecv側のコールバックが呼ばれるまで1秒のラグがあると思います。これはrecv側がデータを受信する処理(
wcli.sync()
)が1秒に1回しか呼ばれていないためです。 これを改善するにはsync()を呼ぶ頻度を上げるか、 4-1. Client のページで説明していますがwcli.sync(timeout)
などが使えます。
Value, Textについては 5-1. Value, 5-2. Text に詳細なドキュメントがあります。
Func
データを送信するだけでなく、プロセス間で関数を呼び出すこともできます。 このチュートリアルではsend側の関数をrecv側から呼び出します。
C++
- send.cc で引数なしの関数hogeを作り、これをクライアントに登録します。
#include <thread>
int hoge() {
std::cout << "Function hoge started" << std::endl;
return 42;
}
int main() {
wcli.func("hoge").set(hoge);
while (true){
wcli.sync();
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
- recv.cc では1秒おきにそれを呼び出します。
int main() {
wcli.waitConnection();
std::cout << "Hello, World! (receiver)" << std::endl;
while (true) {
std::cout << "Error in hoge(): " << hoge_p.rejection() << std::endl;
}else{
std::cout << "hoge() = " << hoge_p.response().asInt() << std::endl;
}
});
wcli.sync();
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
Promise runAsync(Args... args) const
関数を実行する (非同期)
Definition func.h:523
Func func(std::string_view field="") const
Definition field.cc:66
非同期で実行した関数の実行結果を取得するインタフェース。
Definition func_result.h:50
bool isError() const
関数がエラーになったかどうかを返す
Definition func_result.cc:235
Promise & onFinish(std::function< void(Promise)> callback)
関数の実行が完了した時呼び出すコールバックを設定
Definition func_result.cc:149
実行すると、send側とrecv側が両方起動していればsend側では1秒おきに hoge() が呼び出され(Function hoge started
と表示され)、recv側ではhoge()の戻り値である42が表示されるはずです。
send側が起動していない状態でrecv側だけを起動すると呼び出しは失敗し、エラー表示になります。 またsend側の関数の中でthrowをした場合にもエラーになります。
- Note
- 関数の呼び出しに1秒程度ラグがあると思います。 これは関数を実際に実行する処理が wcli.sync() (このチュートリアルでは1秒に1回呼んでいる) の中で行われているためです。 これを改善するにはsync()を呼ぶ頻度を上げるか、 4-1. Client のページで説明していますが
wcli.loopSync()
などが使えます。
- また、send側では呼び出された関数の実行が完了するまで wcli.sync() は完了しません。 メインループをブロックせず別スレッドで関数を呼び出したい場合は set() の代わりに setAsync() が使えます。
こんどは引数がある関数を作ってみます。
- send.cc
int fuga(int a, const std::string &b) {
logger << "Function fuga(" << a << ", " << b << ") started" << std::endl;
return a;
}
int main() {
wcli.func("hoge").set(hoge);
wcli.func("fuga").set(fuga);
}
- recv.cc
int main() {
wcli.waitConnection();
std::cout << "Hello, World! (receiver)" << std::endl;
while (true) {
std::cout << "Error in hoge(): " << hoge_p.rejection() << std::endl;
}else{
std::cout << "hoge() = " << hoge_p.response().asInt() << std::endl;
}
});
std::cout << "Error in fuga(123, abc): " << fuga_p.rejection() << std::endl;
}else{
std::cout << "fuga(123, abc) = " << fuga_p.response().asInt() << std::endl;
}
});
wcli.sync();
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
引数は整数型、実数型、bool、文字列型であればいくつでも使うことができます。 呼び出す側ではrunAsync()に引数を渡せば、引数のない関数と同様に呼び出すことができます。
Funcについては 5-3. Func に詳細なドキュメントがあります。
Python
- send.cc で引数なしの関数hogeを作り、これをクライアントに登録します。
from webcface import Client
import time
wcli = Client("tutorial-send")
@wcli.func("hoge")
def hoge() -> int:
print("Function hoge started")
return 42
wcli.wait_connection()
while True:
wcli.sync()
time.sleep(1)
- recv.py では1秒おきにそれを呼び出します。
from webcface import Client, Promise
import time
wcli = Client("tutorial-recv")
wcli.wait_connection()
print("Hello, World! (receiver)")
sender = wcli.member("tutorial-send")
while True:
hoge_p = sender.func("hoge").run_async()
@hoge_p.on_finish
def on_hoge_finish(hoge_p: Promise):
if hoge_p.is_error:
print(f"Error in hoge(): {hoge_p.rejection}")
else:
print(f"hoge() = {hoge_p.response()}")
wcli.sync()
time.sleep(1)
実行すると、send側とrecv側が両方起動していればsend側では1秒おきに hoge() が呼び出され(Function hoge started
と表示され)、recv側ではhoge()の戻り値である42が表示されるはずです。
send側が起動していない状態でrecv側だけを起動すると呼び出しは失敗し、エラー表示になります。 またsend側の関数の中で例外をraiseした場合にもエラーになります。
- Note
- 関数の呼び出しに1秒程度ラグがあると思います。 これは関数を実際に実行する処理が wcli.sync() (このチュートリアルでは1秒に1回呼んでいる) の中で行われているためです。 これを改善するにはsync()を呼ぶ頻度を上げるか、 4-1. Client のページで説明していますが
wcli.sync(timeout)
などが使えます。
- また、send側では呼び出された関数の実行が完了するまで wcli.sync() は完了しません。 メインループをブロックせず別スレッドで関数を呼び出したい場合は set() の代わりに set_async() が使えます。
こんどは引数がある関数を作ってみます。
- send.py
from webcface import Client
wcli = Client("tutorial-send")
@wcli.func("fuga")
def fuga(a: int, b: str) -> int:
print(f"Function fuga({a}, {b}) started")
return a
wcli.wait_connection()
- recv.py
from webcface import Client, Promise
import time
wcli = Client("tutorial-recv")
wcli.wait_connection()
print("Hello, World! (receiver)")
sender = wcli.member("tutorial-send")
while True:
hoge_p = sender.func("hoge").run_async()
@hoge_p.on_finish
def on_hoge_finish(hoge_p: Promise):
if hoge_p.is_error:
print(f"Error in hoge(): {hoge_p.rejection}")
else:
print(f"hoge() = {hoge_p.response()}")
fuga_p = sender.func("fuga").run_async(123, "abc")
@fuga_p.on_finish
def on_fuga_finish(fuga_p: Promise):
if fuga_p.is_error:
print(f"Error in fuga(): {fuga_p.rejection}")
else:
print(f"fuga() = {fuga_p.response()}")
wcli.sync()
time.sleep(1)
引数は整数型、実数型、bool、文字列型であればいくつでも使うことができます。 呼び出す側ではrunAsync()に引数を渡せば、引数のない関数と同様に呼び出すことができます。
Funcについては 5-3. Func に詳細なドキュメントがあります。
おわりに
以上で 1-2. Tutorial (Communication) は終わりです。
ここで紹介していない機能として
も送受信することができるデータとしてあるので、見てみてください。
また、チュートリアルでは紹介していないコマンドラインツールとして
もあります。