ROSの開発環境でflaskのrender_templateを使う


簡単ではあるが、地味に記事がない内容だったので、まとめておく

# 動作環境

|項目|説明|
|---|---|
|OS|Ubuntu 20.04.05 LTS |
|ROSディストリビューション|noetic|
|Flask|2.2.2|
|Python3|3.9.14|

# ROSでflaskを使う

## 参考サイト

https://answers.ros.org/question/234418/easiest-way-to-implement-http-server-that-can-send-ros-messages/

## 手順

```
$ catkin_make
$ source devel/setup.bash
```

を使用した後、新たなプロジェクトとして

```
$ cd src
$ catkin_create_pkg ros_flask_test
```

として、ros_flask_testというプロジェクトを作成。

ros_flask_test内は、（例えばVisual Studio Codeを使うとして）
以下のフォルダ構成とする

![Screenshot from 2022-11-25 15-42-12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281173/ec341cdd-38fd-cac1-0580-ebf03f4db8f3.png)

flask_server.pyはinit_nodeとしてros_flask_testを、
ローカルホストでポート番号を11000として使うとすると

```python:flask_server.py
import rospy
import threading
from flask import Flask

app = Flask(__name__)  
    
threading.Thread(target=lambda: rospy.init_node('ros_flask_test', disable_signals=True)).start()

@app.route('/')
def root_page():
  return "Hello World"

if __name__ == '__main__':    
  app.run(debug=True, port=11000)
```
このプログラムでは使わないが、some_page.htmlとして

```html:some_page.html
<html>
<body>
Test Page
</body>
</html>
```

CMakelists.txt

```cmake:CMakelists.txt
cmake_minimum_required(VERSION 3.0.2)
project(ros_flask_test)

find_package(catkin REQUIRED)

catkin_package(
)

include_directories(
)

catkin_install_python(
  PROGRAMS
  scripts/flask_server.py
  DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
)
```

として、プロジェクトより上のフォルダに移動して

```
$ catkin_make
```

とすることで、ビルド完了。

```
$ rosrun ros_flask_test flask_server.py
```

を実行することで、

![Screenshot from 2022-11-25 15-45-31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281173/908a3e17-2ef0-0d75-902f-4a4b06c3f42d.png)

ちゃんとサーバが実行され、

![Screenshot from 2022-11-25 15-46-01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281173/f02e2e75-93f3-fea7-942f-128f58e1d576.png)

Hello Worldが表示された。

この方法はWeb APIの方法に近いと思われる。

# ROS(開発環境)でrender_templateを使いたい

## 上手く行かない例

素直に、先程のflask_server.pyを拡張する。

```python:flask_server.py
import rospy
import threading
from flask import Flask, render_template

app = Flask(__name__)  
print(app.root_path)            # ここをチェックする
    
threading.Thread(target=lambda: rospy.init_node('ros_flask_test', disable_signals=True)).start()

@app.route('/')
def root_page():
  return "Hello World"

@app.route('/some_page')
def some_page():
  return render_template("some_page.html")

if __name__ == '__main__':    
  app.run(debug=True, port=11000)

```

つまり、
```
http://127.0.0.1:5000/some_page
```
を開けば、some_page.htmlがレンダリングされて表示されるはず。シンプルな内容だ。

ところが、これを実行すると

![Screenshot from 2022-11-25 15-49-40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281173/c1905eb2-b11f-8198-af82-ee63bda31852.png)

TemplateNotFoundエラーが出る。

まあ、通常のflaskであれば、このディレクトリ構成で良いのだが、
要はディレクトリが良くないわけだ。

## 改善

というわけで、**# ここをチェックする**の行で出てきたprintで何が出てきたかを調べると、

〜(rospyを実行しているディレクトリ)/devel/lib/ros_flask_test

となっていることが分かる。

そして、確かによく見てみると

![Screenshot from 2022-11-25 15-53-07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281173/355aed95-108c-0595-1e66-20c2976e0f06.png)

devel/libフォルダにもpyファイルがあるのだ。

ただここは開発環境用の自動スクリプトであるから、
ここを編集して、というわけにはいかない。

そこで、flaskのroot_pathを常に変えられるように実行する。

ros_flank_testのフォルダの下にlaunchフォルダを作成し、
local_html_render.launchというファイルをとりあえず作る（何でも良い）。

```xml:local_html_render.launch
<launch>
    <node pkg="ros_flask_test" type="flask_server.py" 
          name="flask_server" respawn="true" output="screen">
        <param name="flask_root_path" value="$(find ros_flask_test)/scripts/" />
    </node>
</launch>
```

とし、

flask_server.pyを

```python:flask_server.py
import rospy
import threading
from flask import Flask, render_template
import rosparam

root_path = rosparam.get_param("/flask_server/flask_root_path")
if root_path != "":
    app = Flask(__name__, root_path=root_path)
else:
    app = Flask(__name__)    
    
threading.Thread(target=lambda: rospy.init_node('ros_flask_test', disable_signals=True)).start()

@app.route('/')
def root_page():
  return "Hello World"

@app.route('/some_page')
def some_page():
  return render_template("some_page.html")

if __name__ == '__main__':    
  app.run(debug=True, port=11000)
```

という風に書き換える。つまり、launchファイルを先に用意しておいて、ローカルファイルのtemplatesフォルダをバインド出来るようにするのだ。

そして、実行方法はrosrunではなく、roslaunchを使用して

```
$ roslaunch ros_flask_test local_html_render.launch
```

とすることで、きちんと実行されたことを確認した。

![Screenshot from 2022-11-25 16-00-59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281173/b90ea66b-a3bd-b02c-6750-1be4f9b5f8d1.png)

なお、上記のプログラムの場合、もしlocalフォルダを指定しない場合は、
<param name="flask_root_path" value="$(find ros_flask_test)/scripts/" />
のvalueを""
とすれば良い。