10
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

ElixirによるROS2クライアントライブラリRclexを、DDS観点で試す

Posted at

はじめに (ROS2って、DDSって、Rclexってなに?)

ROS2(Robot Operating System)はロボット開発支援フレームワークです。ROS2環境ではプログラムコードはC++やPythonで書かれることが主に想定されているようですが、これをElixirでも開発したい!ということでElixirのROS2クライアントライブラリ(=RCL)、Rclexが開発されています。

ROS2は通信レイヤにDDS(Data Distribution Service)という通信プロトコル/ライブラリ/パッケージが採用されていて、開発言語を問わず実装にDDSを用いていれば、ノード間で簡単に通信することが可能です(C++で書かれたROS2ノードとPythonで書かれたROS2ノード間で簡単に通信可能)。このRclexを用いればElixirで書いたコードで他のROS2ノードと通信することが可能です。

この記事はRclexを試してみましたので、そのノウハウを紹介するものです。

(将来的に、Elixir+DDS(ROS2なし)で相互通信できるようになる/することを想定しています。なおこの記事(Rclex)はElixir+ROS2(within DDS)環境です。)

DDSについては(ROS2観点ですが)こちらの記事が参考になります。
https://ros.youtalk.jp/2017/05/28/dds.html

なお本記事は、2021年5月時点の内容です。

環境とバージョン

Rclexは環境を選びます。以下の環境で動かしました。

  • Ubuntu 18.04.5 LTS (amd64)
  • ROS2 Dashing Diademata - Patch Release 8
  • Elixir 1.11.2
  • Erlang/OTP 24

スクリーンショット 2021-05-25 11.35.35.png

当初Macでトライしましたが、ROS2は入るのですがRclexがビルドできず断念しました。
Ubuntuは、Ubuntu 20.04.2.0 LTS (Focal Fossa)ではROS2 Dashingがインストールできません。ROS2の新しいリリース(Galactic Geochelone)あたりは入るのですが、今度はROS2 GGではRclexがビルドできません。そのため18.04が必要です。
そういう訳でROS2はDashingでなければなりません。前述のように現状のRclexはDashingのライブラリ構造に依存しているためです。(コードをすごく頑張って直せば対応できるはずですが...。)
ErlangとElixirはUbuntu環境でパッケージインストールしました。特に問題ありませんでした。

環境構築、インストール

Ubuntu 18.04.5環境の構築

私はUbuntuをintel Mac(10.15 Catalina)上にVirtualBoxをインストールしその上で動かしました。ROS2に不慣れなのでGUIツール(rviz2など)も使うかなと思ったためですが、今回のようにDDSで通信を試すだけであればGUIは不要です。たぶんクラウド上のVMでも大丈夫だと思います。

VirtualBoxのインストール、またUbuntuのインストールは割愛します。調べればすぐに見つかるでしょう。
OS本体のインストールが完了したら、VirtualBoxのGuest Additionsもインストールしておきましょう。Guest AddtionsはROS2/Rclexトライアル的には不要ですが、入れたほうが便利です。

bash
% sudo apt-get update
% sudo apt-get install virtualbox-guest-dkms

このあたりを参考にしました。
https://www.linuxmania.jp/virtualbox_02.html

ROS2 Dashingのインストール

Ubuntu環境ができたら、ROS2 Dashingをインストールしていきます。ROS2はバイナリパッケージインストールと自前構築の2つの方法がありますが、ここではソースコードから自前で構築しました。後述のRclexのビルドのために、includeファイルとライブラリの場所を把握しておきたかったためです。(バイナリパッケージでインストールしてもうまくできるかもしれませんが、未試用です。)

基本的な手順はROS2のドキュメント通りです。簡単に解説していきます。
https://docs.ros.org/en/dashing/Installation/Ubuntu-Development-Setup.html
(なおROS2のドキュメントは、バージョンが異なっても全般的には非常に似ていますので注意が必要です。)

以下の手順では、ROS2のフルセットをインストールしていますが、多分、いらない物がたくさんあるはずなので、ROS2に詳しい方は不要なもののインストールは省略できるはずです。RclexのビルドにはROS2の開発環境があればよいはず。

ロケールをセット

bash
% sudo apt update && sudo apt install locales
% sudo locale-gen en_US en_US.UTF-8
% sudo update-locale LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8
% export LANG=en_US.UTF-8

ちなみに、この設定をしたのに、あとの方でコケました。

ROS2レポジトリを追加

bash
% sudo apt update && sudo apt install curl gnupg2 lsb-release
% sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key  -o /usr/share/keyrings/ros-archive-keyring.gpg

% echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] http://packages.ros.org/ros2/ubuntu $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/ros2.list > /dev/null

この手順に失敗すると、次の手順のパッケージインストールの際に謎のエラーが出るので、間違えないように注意しましょう。

環境構築に必要なパッケージをインストール

bash
% sudo apt update && sudo apt install -y \
  build-essential \
  cmake \
  git \
  python3-colcon-common-extensions \
  python3-pip \
  python-rosdep \
  python3-vcstool \
  wget
% python3 -m pip install -U \
  argcomplete \
  flake8 \
  flake8-blind-except \
  flake8-builtins \
  flake8-class-newline \
  flake8-comprehensions \
  flake8-deprecated \
  flake8-docstrings \
  flake8-import-order \
  flake8-quotes \
  pytest-repeat \
  pytest-rerunfailures \
  pytest \
  pytest-cov \
  pytest-runner \
  setuptools
% sudo apt install --no-install-recommends -y \
  libasio-dev \
  libtinyxml2-dev
% sudo apt install --no-install-recommends -y \
  libcunit1-dev

ROS2 Dashing本体コードをダウンロード

bash
% mkdir -p ~/ros2_dashing/src
% cd ~/ros2_dashing
% wget https://raw.githubusercontent.com/ros2/ros2/dashing/ros2.repos
% vcs import src < ros2.repos

続いて、rosdepコマンドで依存関係パッケージをインストールしていきます。

bash
% sudo rosdep init
% rosdep update
% rosdep install --from-paths src --ignore-src --rosdistro dashing -y --skip-keys "console_bridge fastcdr fastrtps libopensplice67 libopensplice69 rti-connext-dds-5.3.1 urdfdom_headers"

ドキュメントではこの後、独自のDDS環境をインストールしたい場合には入れよ、と書いてあるのですが、ここではそれはしません。

コンパイル

いよいよROS2本体をビルドしていきます。

bash
% cd ~/ros2_dashing/
% colcon build --symlinck-install

非常に時間がかかります。Corei7/32GBのMac、VirtualBox 4GBメモリ環境で、1時間半くらいかかりました。

環境変数をセット

ビルドが終わった時点で、ROS2環境が ~/ros2_dashing/install配下にできています。ROS2環境を使う(動かす)ためには環境変数(PATH等)をセットする必要があり、それをするためのスクリプトが用意されています。ターミナル実行時に都度環境変数をセットするか(下記)、.bashrc等でシェル起動時に自動で読み込ませるかします。

bash
% . ~/ros2_dashing/install/setup.bash

bash以外のシェル用のスクリプトもあります。(sh, zah, powershell)

簡単にサンプルを動かしてみる

ROS2の環境構築ができたところで、ドキドキしながらサンプルを動かしてみます。
ターミナルを2つ起動し、双方でsetup.bashを実行させてros2コマンドが動くようにしておきます(下記には含んでいます)。その状態で、

ターミナル1
% . ~/ros2_dashing/install/local_setup.bash
% ros2 run demo_nodes_cpp talker
ターミナル2
% . ~/ros2_dashing/install/local_setup.bash
% ros2 run demo_nodes_py listener

とすると、ターミナル1側でpublishした文字列が、ターミナル2側のsubscriber側で受信できています。やりました!

実際の実行時の画面はこんな感じです。

スクリーンショット 2021-05-25 12.43.51.png

なお、実行時にエラーが出る可能性があります。% export LANG=en_US.UTF-8 を再度設定する必要がありました。
(新しいターミナルを起動して、そちらではLANGが設定されていなかったからなのかもしれません。ログがなく詳細不明です。)

Elixir環境の構築

ROS2環境ができたので、ようやくRclexですが、前提となるElixirを入れます。

Elixirのドキュメントに従います。
https://elixir-lang.jp/install.html

ddstest2という作業ディレクトリを掘ってやりました。

bash
% mkdir ddstest2
% cd ddstest2
% wget https://packages.erlang-solutions.com/erlang-solutions_1.0_all.deb && sudo dpkg -i erlang-solutions_1.0_all.deb
% sudo apt-get update
% sudo apt-get install esl-erlang
% sudo apt-get install elixir

その他、Rclexのビルドに必要なパッケージを入れておきます。

bash
% sudo apt-get install build-essential
% sudo apt-get install erlang-dev

Rclexをインストールする

ここからがようやく本番です!
Rclexはライブラリであり、単体で使用するものではないので、ここでは同じ開発者から提供されているサンプルコードを使います。Rclex本体はサンプルコードの中で依存パッケージとして指定されており、サンプルコードをビルドするときに自動的に、インストール・ビルドされます。

bash
% git clone https://github.com/rclex/rclex_samples.git
% cd rclex_samples

今回はただサンプルコードをそのまま使います。Elixirの流儀に従って作業します。

bash
kikuzo@kikuzo-VirtualBox:~/ddstest2/rclex_samples$ mix deps.get
Resolving Hex dependencies...
Dependency resolution completed:
Unchanged:
  elixir_make 0.6.2
  rclex 0.3.1
* Getting rclex (Hex package)
* Getting elixir_make (Hex package)
kikuzo@kikuzo-VirtualBox:~/ddstest2/rclex_samples$ 

すかさずmixコマンドを実行してみると、

bash
kikuzo@kikuzo-VirtualBox:~/ddstest2/rclex_samples$ mix
==> elixir_make
Compiling 1 file (.ex)
Generated elixir_make app
==> rclex
mkdir -p /home/kikuzo/ddstest2/rclex_samples/_build/dev/lib/rclex/priv
mkdir -p /home/kikuzo/ddstest2/rclex_samples/_build/dev/lib/rclex/obj
gcc -c -I/usr/lib/erlang/usr/include -I/opt/ros/dashing/include -g -O2 -Wall -Wextra -Wno-unused-parameter -pedantic -fPIC -o /home/kikuzo/ddstest2/rclex_samples/_build/dev/lib/rclex/obj/total_nif.o src/total_nif.c
gcc -c -I/usr/lib/erlang/usr/include -I/opt/ros/dashing/include -g -O2 -Wall -Wextra -Wno-unused-parameter -pedantic -fPIC -o /home/kikuzo/ddstest2/rclex_samples/_build/dev/lib/rclex/obj/init_nif.o src/init_nif.c
src/init_nif.c:10:10: fatal error: rcl/rcl.h: No such file or directory
 #include "rcl/rcl.h"
          ^~~~~~~~~~~
compilation terminated.
Makefile:43: recipe for target '/home/kikuzo/ddstest2/rclex_samples/_build/dev/lib/rclex/obj/init_nif.o' failed
make: *** [/home/kikuzo/ddstest2/rclex_samples/_build/dev/lib/rclex/obj/init_nif.o] Error 1
could not compile dependency :rclex, "mix compile" failed. You can recompile this dependency with "mix deps.compile rclex", update it with "mix deps.update rclex" or clean it with "mix deps.clean rclex"
==> rclex_samples
** (Mix) Could not compile with "make" (exit status: 2).
You need to have gcc and make installed. If you are using
Ubuntu or any other Debian-based system, install the packages
"build-essential". Also install "erlang-dev" package if not
included in your Erlang/OTP version. If you're on Fedora, run
"dnf group install 'Development Tools'".

kikuzo@kikuzo-VirtualBox:~/ddstest2/rclex_samples$ 

コンパイルエラーです。これは、RclexをコンパイルするのにROS2環境側のインクルードファイルが見つからないのが原因です。Rclexが期待するROS2環境のディレクトリ構成と↑で作業したディレクトリ構成が異なっているためで、ここでは、RclexのMakefileの該当部分を修正してしまいます。

~/ddstest2/rclex_samples/deps/rclex/Makefile
# set directory for ROSDISTRO
ROSDIR = /opt/ros/dashing

CC = gcc
LD = ld
RM = rm

PREFIX = $(MIX_APP_PATH)/priv
BUILD  = $(MIX_APP_PATH)/obj

NIF = $(PREFIX)/rclex.so

CFLAGS  ?= -g -O2 -Wall -Wextra -Wno-unused-parameter -pedantic -fPIC
LDFLAGS ?= -g -shared

# Set Erlang-specific compile and linker flags
ERL_CFLAGS  ?= -I$(ERL_EI_INCLUDE_DIR)
ERL_LDFLAGS ?= -L$(ERL_EI_LIBDIR)

# for ROS libs
ROS_CFLAGS  ?= -I$(ROSDIR)/include
ROS_LDFLAGS ?= -L$(ROSDIR)/lib
ROS_LDFLAGS += -lrcl -lrmw -lrcutils \
        -lrosidl_generator_c -lrosidl_typesupport_c \
        -lrosidl_typesupport_introspection_c \
        -lstd_msgs__rosidl_generator_c -lstd_msgs__rosidl_typesupport_c \
        -lfastcdr -lfastrtps -lrmw_fastrtps_cpp
# if you want to use OpenSplice DDS
# ROS_LDFLAGS    += -lrmw_opensplice_cpp -lrosidl_typesupport_opensplice_cpp

SRC ?= src/total_nif.c src/init_nif.c src/node_nif.c src/publisher_nif.c src/subscription_nif.c src/wait_nif.c
SRC += src/msg_int16_nif.c src/msg_string_nif.c
HEADERS =$(wildcard src/*.h)
OBJ = $(SRC:src/%.c=$(BUILD)/%.o)

all: install

install: $(PREFIX) $(BUILD) $(NIF)

$(OBJ): $(HEADERS) Makefile

$(BUILD)/%.o: src/%.c
        $(CC) -c $(ERL_CFLAGS) $(ROS_CFLAGS) $(CFLAGS) -o $@ $<

$(NIF): $(OBJ)
        $(CC) -o $@ $(ERL_LDFLAGS) $(LDFLAGS) $^ $(ROS_LDFLAGS)

$(PREFIX):
        mkdir -p $@

$(BUILD):
        mkdir -p $@

clean:
        $(RM) $(NIF) $(BUILD)/*.o

.PHONY: all clean install

冒頭部分のROSDIRを修正します。

~/ddstest2/rclex_samples/deps/rclex/Makefile
# set directory for ROSDISTRO
# ROSDIR = /opt/ros/dashing
ROSDIR = /home/kikuzo/ros2_dashing/install

CC = gcc
LD = ld
RM = rm

PREFIX = $(MIX_APP_PATH)/priv
BUILD  = $(MIX_APP_PATH)/obj

(..省略..)

できたら、rclexをビルドします。↑のエラーメッセージ内にありましたが、rclex本体を作るためには

bash
% mix deps.compile rclex

とやります。

bash
kikuzo@kikuzo-VirtualBox:~/ddstest2/rclex_samples$ mix deps.compile rclex
==> rclex
mkdir -p /home/kikuzo/ddstest2/rclex_samples/_build/dev/lib/rclex/priv
mkdir -p /home/kikuzo/ddstest2/rclex_samples/_build/dev/lib/rclex/obj
gcc -c -I/usr/lib/erlang/usr/include -I/home/kikuzo/ros2_dashing/install/include -g -O2 -Wall -Wextra -Wno-unused-parameter -pedantic -fPIC -o /home/kikuzo/ddstest2/rclex_samples/_build/dev/lib/rclex/obj/total_nif.o src/total_nif.c
gcc -c -I/usr/lib/erlang/usr/include -I/home/kikuzo/ros2_dashing/install/include -g -O2 -Wall -Wextra -Wno-unused-parameter -pedantic -fPIC -o /home/kikuzo/ddstest2/rclex_samples/_build/dev/lib/rclex/obj/init_nif.o src/init_nif.c
src/init_nif.c:10:10: fatal error: rcl/rcl.h: No such file or directory
 #include "rcl/rcl.h"
          ^~~~~~~~~~~
compilation terminated.
Makefile:45: recipe for target '/home/kikuzo/ddstest2/rclex_samples/_build/dev/lib/rclex/obj/init_nif.o' failed
make: *** [/home/kikuzo/ddstest2/rclex_samples/_build/dev/lib/rclex/obj/init_nif.o] Error 1
could not compile dependency :rclex, "mix compile" failed. You can recompile this dependency with "mix deps.compile rclex", update it with "mix deps.update rclex" or clean it with "mix deps.clean rclex"
==> rclex_samples
** (Mix) Could not compile with "make" (exit status: 2).
You need to have gcc and make installed. If you are using
Ubuntu or any other Debian-based system, install the packages
"build-essential". Also install "erlang-dev" package if not
included in your Erlang/OTP version. If you're on Fedora, run
"dnf group install 'Development Tools'".

kikuzo@kikuzo-VirtualBox:~/ddstest2/rclex_samples$ 

再びエラー。依然としてファイルがないと言われます。

本来はMakefileを直したほうが良いのですが、どうやら前提とするディレクトリ構成が異なっているようで修正が大きそうなので、ROS2環境側でシンボリックリンクを張って回避することにします。(このあたり、Rclex開発時はどうだったのかちょっと謎です。ROS2 Dashingのパッチレベルが上ってディレクトリ構成が変わったのかな?)

bash
% cd ~/ros2_dashing/install
% mkdir include
% cd include
% ln -s ../rcl/include/rcl rcl
(同様に、rcutils, rmw, rosidl_generator_c, rosidl_typesupport_interface, std_msgsについても市リンクを張ります)

このようになります。
スクリーンショット 2021-05-25 13.42.53.png

さて今度こそ。

bash
kikuzo@kikuzo-VirtualBox:~/ddstest2/rclex_samples$ mix deps.compile rclex
==> rclex
(..省略..)
gcc -o /home/kikuzo/ddstest2/rclex_samples/_build/dev/lib/rclex/priv/rclex.so -L/usr/lib/erlang/usr/lib -g -shared /home/kikuzo/ddstest2/rclex_samples/_build/dev/lib/rclex/obj/total_nif.o /home/kikuzo/ddstest2/rclex_samples/_build/dev/lib/rclex/obj/init_nif.o /home/kikuzo/ddstest2/rclex_samples/_build/dev/lib/rclex/obj/node_nif.o /home/kikuzo/ddstest2/rclex_samples/_build/dev/lib/rclex/obj/publisher_nif.o /home/kikuzo/ddstest2/rclex_samples/_build/dev/lib/rclex/obj/subscription_nif.o /home/kikuzo/ddstest2/rclex_samples/_build/dev/lib/rclex/obj/wait_nif.o /home/kikuzo/ddstest2/rclex_samples/_build/dev/lib/rclex/obj/msg_int16_nif.o /home/kikuzo/ddstest2/rclex_samples/_build/dev/lib/rclex/obj/msg_string_nif.o -L/home/kikuzo/ros2_dashing/install/lib -lrcl -lrmw -lrcutils -lrosidl_generator_c -lrosidl_typesupport_c -lrosidl_typesupport_introspection_c -lstd_msgs__rosidl_generator_c -lstd_msgs__rosidl_typesupport_c -lfastcdr -lfastrtps -lrmw_fastrtps_cpp
/usr/bin/ld: cannot find -lrcl
collect2: error: ld returned 1 exit status
Makefile:48: recipe for target '/home/kikuzo/ddstest2/rclex_samples/_build/dev/lib/rclex/priv/rclex.so' failed
make: *** [/home/kikuzo/ddstest2/rclex_samples/_build/dev/lib/rclex/priv/rclex.so] Error 1
could not compile dependency :rclex, "mix compile" failed. You can recompile this dependency with "mix deps.compile rclex", update it with "mix deps.update rclex" or clean it with "mix deps.clean rclex"
==> rclex_samples
** (Mix) Could not compile with "make" (exit status: 2).
You need to have gcc and make installed. If you are using
Ubuntu or any other Debian-based system, install the packages
"build-essential". Also install "erlang-dev" package if not
included in your Erlang/OTP version. If you're on Fedora, run
"dnf group install 'Development Tools'".

kikuzo@kikuzo-VirtualBox:~/ddstest2/rclex_samples$ 

コンパイルは通りましたが、こんどはライブラリがないようです。上と同様にシンボリックリンクを貼ってしのぐことにします。

bash
% cd ~/ros2_dashing/install
% mkdir lib
% cd lib
% ln -s ../rcl/lib/librcl.so  librcl.so

(
libfastcdr.so           librosidl_generator_c.so
libfastrtps.so          librosidl_typesupport_c.so
librcl.so               librosidl_typesupport_introspection_c.so
librcutils.so           libstd_msgs__rosidl_generator_c.so
librmw_fastrtps_cpp.so  libstd_msgs__rosidlgenerator_c.so
librmw.so               libstd_msgs__rosidl_typesupport_c.so
について同様にします。
)

ちょっと見にくいですがこのようになります。

スクリーンショット 2021-05-25 14.01.19.png

これでコンパイルが通ります。やりました!

bash
kikuzo@kikuzo-VirtualBox:~/ddstest2/rclex_samples$ mix deps.compile rclex
==> rclex
(..省略..)
gcc -o /home/kikuzo/ddstest2/rclex_samples/_build/dev/lib/rclex/priv/rclex.so -L/usr/lib/erlang/usr/lib -g -shared /home/kikuzo/ddstest2/rclex_samples/_build/dev/lib/rclex/obj/total_nif.o /home/kikuzo/ddstest2/rclex_samples/_build/dev/lib/rclex/obj/init_nif.o /home/kikuzo/ddstest2/rclex_samples/_build/dev/lib/rclex/obj/node_nif.o /home/kikuzo/ddstest2/rclex_samples/_build/dev/lib/rclex/obj/publisher_nif.o /home/kikuzo/ddstest2/rclex_samples/_build/dev/lib/rclex/obj/subscription_nif.o /home/kikuzo/ddstest2/rclex_samples/_build/dev/lib/rclex/obj/wait_nif.o /home/kikuzo/ddstest2/rclex_samples/_build/dev/lib/rclex/obj/msg_int16_nif.o /home/kikuzo/ddstest2/rclex_samples/_build/dev/lib/rclex/obj/msg_string_nif.o -L/home/kikuzo/ros2_dashing/install/lib -lrcl -lrmw -lrcutils -lrosidl_generator_c -lrosidl_typesupport_c -lrosidl_typesupport_introspection_c -lstd_msgs__rosidl_generator_c -lstd_msgs__rosidl_typesupport_c -lfastcdr -lfastrtps -lrmw_fastrtps_cpp
Compiling 6 files (.ex)
warning: redefining @doc attribute previously set at line 435.

Please remove the duplicate docs. If instead you want to override a previously defined @doc, attach the @doc attribute to a function head:

    @doc """
    new docs
    """
    def initialize_msgs(...)

  lib/rclex.ex:445: Rclex.initialize_msgs/2

Generated rclex app
kikuzo@kikuzo-VirtualBox:~/ddstest2/rclex_samples$ 

動かしてみる

rclex_samplesにある、simple_pubを使ってみましょう。
ターミナルを2つ起動し、一つでrclex_samples/simple_pubを動かして送信しし、もう一つはROS2のコマンドでsubscriberを動かして受信してみます。

bash
kikuzo@kikuzo-VirtualBox:~/ddstest2/rclex_samples$ iex -S mix
Erlang/OTP 24 [erts-12.0] [source] [64-bit] [smp:1:1] [ds:1:1:10] [async-threads:1] [jit]

Interactive Elixir (1.11.2) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> RclexSamples.SimplePub. 
callback/1    pub_main/1    
iex(1)> RclexSamples.SimplePub.pub_main 1
finished rcl_init
                 publish message:hello,world
publish ok

実際の様子。

スクリーンショット 2021-05-25 14.50.13.png

受信側はこうですね。

スクリーンショット 2021-05-25 14.47.53.png

なお、iexコマンドの起動に先立って、ros2環境のセットアップ(setup.bashの実行)をしておかないと実行時にエラーが出ます。

まとめ

Rclexを動かしてみました。DDSでメッセージを送受信するだけですが、とりあえず動いていることは確認できました。

みなさんも、いろいろ試してみて、様子を教えて下さい。

10
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?