NigoroJr

I-O DATA WNPU583BをUbuntuで使用する

I-O DATA WNPU583B は,デバイスひとつで802.11acとBluetooth 4.0が使用できるUSBドングルです.I-O DATAが2016年末に立ち上げた新ブランド「PLANT」の製品だそうな.Amazonで見たときはパチモンかと思いましたが,Ubuntuで使用できたので,行ったことをまとめます.

WNPU583Bはrtl8812auドライバが必要ですが,執筆時点ではUbuntu 16.04, 18.04に入っていません.以下のリポジトリからドライバをダウンロードしてきて,

$ make -j8
$ sudo make install
$ sudo modprobe 8812au

を実行しましょう.

上のリンクは私のフォーク,下が本家です.WNPU583Bが使えるようになるパッチが本家でマージされるまでは,上のリンクを使用してください.

動作を確認した環境は以下の通り:

  • Ubuntu 16.04
    • 4.15.0-52-generic #56~16.04.1-Ubuntu SMP Thu Jun 6 12:03:31 UTC 2019
  • Ubuntu 18.04
    • 4.18.0-18-generic #19~18.04.1-Ubuntu SMP Fri Apr 5 10:22:13 UTC 2019

Wi-FiとBluetoothどちらも動作を確認しましたが,いずれも「接続ができる」というレベルの確認のため,パフォーマンスについては追い追い調べられたらなと.

追記(2019/07/11)

なんとなく,動作が不安定な気がします.デバイスが接続されたときのudevの処理がいけないのか,同じデバイスが3つ登録されていたり,アクセスポイントに接続するのにやたら時間がかかったり...

Amazonのレビューにも,安定して動作するという人と,動作が不安定という人がいたので,もしかしたらデバイス自体の完成度がまだ高まっていないのかも…?

Using matplotlib to plot on ROS subscriber callback

by NigoroJr 0 Comments

Objective

Update a plot every time a callback function of a ROS subscriber is called.

TL;DR

Use plt.show() (or fig.show()) instead of rospy.spin(). Call plt.draw() (or fig.canvas.draw()) in the callback function.

Approaches I tried (but didn’t work)

  • matplotlib.animation
    • cannot be used because calls to callback functions are not periodical
  • Calling plt.show() in the callback function
    • plt.show() blocks, so the plot is not updated after the first message
  • Using fig = plt.figure() and running fig.show
    • main thread is not in main loop, since callback function are called in a separate thread

Solution

The solution is to remove rospy.spin() and use plt.show() instead. They do the same thing. That is, all they do is they both simply spin in the main loop.

Following is the code that I used to create a plot every time a callback is fired. What it does is it calculates the curvature of the nav_msgs/Path, but that is irrelevant to what we’re trying to do now.

The important point is to have plt.show() inside the main thread; otherwise, you’ll get a RuntimeError: main thread is not in main loop.

#!/usr/bin/env python

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
sns.set(style='darkgrid')

import rospy
from nav_msgs.msg import Path

from path_smoother import PathConverter, Utils


def cb_path(msg):
    df = PathConverter.path_to_df(msg)
    curvatures = Utils.curvatures(df)

    plt.cla()
    plt.plot(curvatures)
    plt.axhline(cb_path.max, c='darkorange')
    plt.draw_all()


def main():
    rospy.init_node('realtime_curvature_plot')

    cb_path.max = rospy.get_param('~kappa_max', 0.2)

    rospy.Subscriber('path', Path, cb_path)

    plt.axhline(cb_path.max, c='darkorange')
    plt.show()
    # Note: No need to spin! plt.show() spins instead
    # rospy.spin()


if __name__ == '__main__':
    main()

Also, following code snippet uses plt.figure(). Other than that it’s identical to the snippet above.

#!/usr/bin/env python

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
sns.set(style='darkgrid')

import rospy
from nav_msgs.msg import Path

from path_smoother import PathConverter, Utils


def cb_path(msg):
    df = PathConverter.path_to_df(msg)
    curvatures = Utils.curvatures(df)

    cb_path.ax.cla()
    cb_path.ax.plot(curvatures)
    cb_path.ax.axhline(cb_path.max, c='darkorange')
    cb_path.fig.canvas.draw()


def main():
    rospy.init_node('realtime_curvature_plot')

    cb_path.fig = plt.figure()
    cb_path.ax = cb_path.fig.add_subplot(111)

    cb_path.max = rospy.get_param('~kappa_max', 0.2)

    rospy.Subscriber('path', Path, cb_path)

    plt.show()
    # Note: No need to spin! plt.show() spins instead
    # rospy.spin()


if __name__ == '__main__':
    main()

ProTip

If you don’t want to use fig = plt.figure() and create a variable that you have to look after, you can also use plt.plot() with no argument to have an empty plot:

def main():
    rospy.init_node('foo')
    rospy.Subscriber('bar', Baz, cb_func)
    ...
    plt.plot()
    plt.show()

Exporting colored COLLADA for use in Gazebo

by NigoroJr 0 Comments

Objective

Read in an STL file into Blender, add color to it and export it as a DAE file (COLLADA), then feed that into Gazebo.

Problem

The color is not displayed correctly. It becomes darker (or brighter, depending on the emission) than the expected color. Expected color (part of the robot):

However, what I get when loading this onto Gazebo is:

The red in the figure above is lighter than the expected red (ignore the stripes; that’s normal for Gazebo). In this article it is assumed that you already have your model in Blender, colored and ready for export. I will not go through how to set up the environment or write the URDF for your robot.

TL;DR

You can’t automatically export the desired color with COLLADA. You need to manually edit the DAE file exported from Blender and set the emission to proper values. Most of the times it’s enough to set it to the same value as the dispertion value.

Environment

  • Ubuntu 18.04
  • Gazebo 9.5.0
  • Blender 2.79

Details

Firstly, whatever you render using Cycles Render is not refleceted to the exported COLLADA file. Whatever you see when you export it using Blender Render is apparently what you get in the COLLADA file. Except, you don’t. When you use a color other than grayscale, you end up getting a color either lighter or darker than what you expect.

The problem arises from the fact that Blender’s COLLADA exported doesn’t handle colored emissions (actually, it seems like a limitation in Blender Render since there’s only one value to be specified for the emission). Emission is the color that the object itself emits. In other words, it’s the color of the object when seen in pitch black. It takes a value between 0 and 1, where 0 is no light emitted (black) and white at 1.

When you specify a value (say 0.4) for emission in Blender, it exports the COLLADA file as follows:

    <effect id="Red-effect">
      <profile_COMMON>
        <technique sid="common">
          <phong>
            <emission>
              <color sid="emission">0.4 0.4 0.4 1</color>
            </emission>
            <ambient>
              <color sid="ambient">0 0 0 1</color>
            </ambient>
            <diffuse>
              <color sid="diffuse">0.6194061 0.024045 0.02145169 1</color>
            </diffuse>
            <specular>
              <color sid="specular">0.5 0.5 0.5 1</color>
            </specular>
            <shininess>
              <float sid="shininess">40</float>
            </shininess>
            <index_of_refraction>
              <float sid="index_of_refraction">1</float>
            </index_of_refraction>
          </phong>
        </technique>
      </profile_COMMON>
    </effect>

Notice that the emission is all 0.4, which is gray. We want this to be red-ish, so we just replace that part manually with:

            <emission>
              <color sid="emission">0.6194061 0.024045 0.02145169 1</color>
            </emission>

Sadly, there is no way to do this automatically at the moment. This means that if you want to update some of the meshes in Blender and export that to COLLADA, the manual change you just made will be overwritten.

Python ROSパッケージ用にunittestを書いてTravisを通そうとしたら大ゴケした話

by NigoroJr 0 Comments

TL;DR

Python用のROSパッケージで単体テストを書き,industrial_ci.travis.yml を使ってTravisを通すには .travis.yml の変数に CATKIN_CONFIG="--no-install を追加しないといけない.

うまくいったときの様子(rostestnosetests).

キーワード

  • Python
  • rostest
  • Travis CI, industrial_ci
  • unittest
  • nosetests
  • Ubuntu 16.04, ROS Kinetic
  • Ubuntu 18.04, ROS Melodic

bagmetti という,bagファイルのメッセージをフィルタしたり中身を変更したりする便利なパッケージを以前作りました.これに単体テストを書きたい.ついでに,ローカル環境はROS MelodicなのでKineticでもビルドできることが確認できるといいなーと思い,Travisを使います(とはいってもPythonなのでコンパイルは不要だし,外部ライブラリに依存していないので問題ないはずなんですが).

rostestを使った単体テストの実装

ROSは,Pythonの単体テストに関するドキュメントが非常に残念であることに気がつきました.公式のWikiが5年以上前の情報だったり,そもそもstackoverflowとかであまりヒットしない.

とはいうものの,Pythonのunittestのドキュメントは充実しています.rostestを使うのが良さそうだったので,テストケースを軽快に書いていきます.ローカル環境で rostest を実行してpassしたので,これでCIに投げて一段落!と思いきや……

テストが通らない!

industrial_ci というROSのためのCIテンプレートがあったので,それをベースに .travis.yml を書いていきます.で,早速pushしてみたところ,見事にエラーが!

まぁCIでいきなりうまくいくことはないかと思いつつ,修正を加えるのですが,なかなか通らない.どうもテストコードで import している自前のモジュールが見つかっていないらしい.

======================================================================
ERROR: Failure: ImportError (No module named bagmetti.rules.rename)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/usr/lib/python2.7/dist-packages/nose/loader.py", line 418, in loadTestsFromName
    addr.filename, addr.module)
  File "/usr/lib/python2.7/dist-packages/nose/importer.py", line 47, in importFromPath
    return self.importFromDir(dir_path, fqname)
  File "/usr/lib/python2.7/dist-packages/nose/importer.py", line 94, in importFromDir
    mod = load_module(part_fqname, fh, filename, desc)
  File "/home/naoki/ros/workspaces/melodic/tmp/src/bagmetti/test/test_rename.py", line 8, in <module>
    from bagmetti.rules.rename import RenameRule
ImportError: No module named bagmetti.rules.rename

たしかにローカル環境でも,一旦 builddevel を消してから catkin run_tests を実行すると,これが起こる.

どうもこの問題は既知らしく,wikiで触れられていました.

One example is a rostest being run as part of the tests. If the test itself depends on Python code which is e.g. being generated the path containing the generated code is not necessarily on the PYTHONPATH. catkin_make does not provide a mechanism to work around this problem

じゃあどうすれば良いかというと,ビルドと実行を分けてやりなさいということでした. industrial_ci は,ちゃんとそうしている模様なので問題なし.

それなのになぜモジュールが見つからないのかとハマりまくった結果,最終的に catkin の設定で Install Location が指定されていることが分かりました.インストールされる場所が想定と違うので,見つからなかったのか〜と半分働いていない頭で考えているのが現状.

問題の対策

対策は,単純に CATKIN_CONFIG 変数に --no-install を渡してやるというものでした.だいぶ無駄な時間を費してしまったので,だれかの役に立つことを願っています.

nosetestsを使った単体テスト

ああでもないこうでもないと試行錯誤している過程で,nosetestsを使ったらできるんじゃないかと思い,nosetestsでもやってみました.

rostestがROS独自の配信や購読といった仕組みを念頭に置いたテストができるようになっているのに対し,noseは他ノードとやりとりしないようなスクリプトの単体テストに向いています.bagmettiは後者の使い方が多いので,noseでも良いかなーと思った次第です.

結論としては,nosetestsでもrostestでも, --no-install さえ渡してやればどちらでもテストは動くということが分かりました.でも,nosetestsのほうがそっけないです.

nosetests

........
----------------------------------------------------------------------
Ran 8 tests in 0.170s

OK

rostest

[ROSTEST]-----------------------------------------------------------------------


SUMMARY
 * RESULT: SUCCESS
 * TESTS: 0
 * ERRORS: 0
 * FAILURES: 0

rostest log file is in /home/naoki/.ros/log/rostest-nazousagi-1694.log
[Testcase: testtest_filter] ... ok

[ROSTEST]-----------------------------------------------------------------------

[bagmetti.rosunit-test_filter/test_is_exclude][passed]
[bagmetti.rosunit-test_filter/test_is_include][passed]
[bagmetti.rosunit-test_filter/test_is_tf][passed]
[bagmetti.rosunit-test_filter/test_is_time][passed]
[bagmetti.rosunit-test_filter/test_is_topic][passed]
[bagmetti.rosunit-test_filter/test_match_tf][passed]
[bagmetti.rosunit-test_filter/test_match_time][passed]
[bagmetti.rosunit-test_filter/test_match_topic][passed]

SUMMARY
 * RESULT: SUCCESS
 * TESTS: 8
 * ERRORS: 0
 * FAILURES: 0

無事,テストが通るようになりました.みなさんもぜひ楽しいTravis + ROS + ユニットテスト生活を!

ROS_INFO_STREAMでなぜか出力されない問題

by NigoroJr 0 Comments

とりあえず,以下のコードを見ていただきたい.

#include <ros/ros.h>

void get_params() {
    ros::NodeHandle np{"~"};
    float f;
    np.param("foobar", f, 4.2f);

    ROS_INFO_STREAM("exitting get_params with foobar == " << f);
}

int
main(int argc, char* argv[]) {
    ros::init(argc, argv, "sample_node");

    ROS_INFO_STREAM("before calling get_params");
    get_params();
    ROS_INFO_STREAM("after calling get_params");

    ros::spin();

    return 0;
}

一見なんら問題ないこのコードだが,実行するとおかしいことが起こる.

問題

コードを実行すると,出力が以下のようになる:

[ INFO] [/sample_node] [1536863298.820675562]: before calling get_params
[ INFO] [/sample_node] [1536863298.825877817]: exitting get_params with foobar == 42.42

最後の after calling get_params が出力されていない.

解決策

get_params に参照を投げるようにする.つまり,

#include <ros/ros.h>

void get_params(const ros::NodeHandle& np) {
    float f;
    np.param("foobar", f, 4.2f);

    ROS_INFO_STREAM("exitting get_params with foobar == " << f);
}

int
main(int argc, char* argv[]) {
    ros::init(argc, argv, "sample_node");

    auto np = ros::NodeHandle{"~"};
    ROS_INFO_STREAM("before calling get_params");
    get_params(np);
    ROS_INFO_STREAM("after calling get_params");

    ros::spin();

    return 0;
}

出力は以下のようになる:

[ INFO] [/sample_node] [1536863710.670982067]: before calling get_params
[ INFO] [/sample_node] [1536863710.672086878]: exitting get_params with foobar == 42.42
[ INFO] [/sample_node] [1536863710.672117960]: after calling get_params

なぜ起こるか?

正直,謎です.分かる方がいたら教えてください.おそらく ros::NodeHandle 内の参照カウンタ的なものの影響のような気がする.

No PyCharm completion for rospy

Premises

I have a version of ros_comm cloned from GitHub.

  • Ubuntu 16.04
  • ROS Lunar

Problem

In PyCharm Professional 2018.1.1, no completion for rospy was shown. The message said:

Cannot find reference 'init_node' in '__init__.py'

Screenshot of the error message

TL;DR

If $( rospack find rospy )/src exists, add it to PYTHONPATH.

Cause of the Problem

After sourcing setup.bash (and friends), PYTHONPATH points to /path/to/workspace/devel/lib/python2.7/dist-packages. Doing import rospy in the Python script uses the following __init__.py:

# -*- coding: utf-8 -*-
# generated from catkin/cmake/template/__init__.py.in
# keep symbol table as clean as possible by deleting all unnecessary symbols

from os import path as os_path
from sys import path as sys_path

from pkgutil import extend_path

__extended_path = "/home/naoki/ros/workspaces/lunar/hsas/src/ros_comm/clients/rospy/src".split(";")
for p in reversed(__extended_path):
    sys_path.insert(0, p)
    del p
del sys_path

__path__ = extend_path(__path__, __name__)
del extend_path

__execfiles = []
for p in __extended_path:
    src_init_file = os_path.join(p, __name__ + '.py')
    if os_path.isfile(src_init_file):
        __execfiles.append(src_init_file)
    else:
        src_init_file = os_path.join(p, __name__, '__init__.py')
        if os_path.isfile(src_init_file):
            __execfiles.append(src_init_file)
    del src_init_file
    del p
del os_path
del __extended_path

for __execfile in __execfiles:
    with open(__execfile, 'r') as __fh:
        exec(__fh.read())
    del __fh
    del __execfile
del __execfiles

The problem is that PyCharm expects __all__ to be populated in order for the completion to function correctly. That is done in /path/to/workspace/src/ros_comm/clients/rospy/src/rospy/__init__.py (i.e. $( rospack find rospy )/src/rospy).

Solution

Add $( rospack find rospy )/src to the front of PYTHONPATH if it exists. For rospy that’s installed with APT, there’s no src subdirectory (i.e. rospack find rospy gives you /opt/ros/DISTRO/share/rospy in which there is no src subdirectory).

if [[ -d $( rospack find rospy )/src ]]; then
    export PYTHONPATH="$( rospack find rospy )/src:$PYTHONPATH"
fi

After that if you start up PyCharm it should give you completion for rospy.

boost::asio::serial_port::read_someでEnd Of File Exceptionが出る際の対処法

boost::asio::serial_portread_some を使って
シリアルポートから読んだ際に,一瞬にしてEOF Exceptionが
投げられました.デバイスファイルのパスもボーレートも権限も
正しいはずなのになんでだろう??と思って調べたのでメモ.

what():  read_some: End of file

解決法1

stackoverflow.com/questions/45896414/boost-asio-serial-port-end-of-file

$ stty -F /dev/ttyUSB0 raw

解決法2

read_some の第2引数に boost::system::error_code を与えてやる.

auto port = boost::asio::serial_port{};
port.open("/dev/ttyUSB0");
port.set_option(boost::asio::serial_port_base::baud_rate(115200));
auto dest_ptr = get_dest_ptr();
auto dest_size = get_dest_size();
auto err_code = boost::system::error_code{};
port.read_some(boost::asio::buffer(&dest_ptr, dest_size), err_code);

ros_controlでControllerがロードできないときのためのメモ

ハマってめちゃくちゃ時間を無駄にして悔しかったので,メモ.

ロボットを定義するURDFのほうで,

<gazebo>
  <plugin name="gazebo_ros_control" filename="libgazebo_ros_control.so">
    <robotNamespace>/hogehoge</robotNamespace>
  </plugin>
</gazebo>

としたとします.

ネームスペースに関して注意しないといけないことが二つ:

YAMLをロードするときのネームスペースに注意

/hogehoge/my_hoge_controller/type: position_controllers... のように,ロボット名のネームスペースに入れないといけません.

YAMLで言うと,

hogehoge:
  my_hoge_controller:
    type: position_controllers/JointTrajectoryController
    # (省略)

としないといけません.

controller_manager を起動する際のネームスペースに注意

<node ns="hogehoge" name="my_hoge_controller_spawner" pkg="controller_manager" type="controller_manager" args="spawn my_hoge_controller" />

のように,ノードを hogehoge (ロボット名)ネームスペースで起動しないといけません.これに気付かずにハマりました…

個別に指定するよりも,

<group ns="hogehoge" >
  <rosparam command="load" file="YAMLへのパス" />
  <node name="my_hoge_controller_spawner" pkg="controller_manager" ... />
</group>

というふうに <group> に入れるのがいいかもしれませんね.

Ubuntu 16.04にfswatchを入れる

ファイルシステムの変更をモニタするのに便利なfswatch
macOSでは使っていたのですが,Ubuntuには入れていなかったなぁ
と思って入れてみたのでメモをしておきます.

PPAを使う

launchpad.net/~hadret/+archive/ubuntu/fswatch

$ sudo add-apt-repository ppa:hadret/fswatch
$ sudo apt-get update
$ sudo apt-get install fswatch

以上.

自分でコンパイルする

自前でコンパイルしたい場合は以下の依存関係をインストールしてやる
必要があります.

  • inotify-tools
  • automake
  • autopoint
  • texinfo

これらを入れれば何の問題もなく make install できるんですが,
私の環境では入っていなかったのでメモ.

Ubuntu 18.04なら

何もせずとも apt-get install fswatch で入るらしいです(未確認).

github.com/emcrisostomo/fswatch/issues/170

ROSのパッケージの開発をするときのTips

by NigoroJr 0 Comments

覚え書き.

launchファイルで一発起動できるようにする

書いて時間がたったときに,起動手順を覚えていない場合が多い.

開発時からパラメータ指定できるようにする

フレーム名とか特に,開発途中の段階ではハードコードしたくなるが,後からパラメータ化しようとしてもどこをどう変えればいいかがすぐに分からず,億劫になってパラメータ化せず,結局必要になったときにめんどくさいことになる.