【ROS】launchファイルでcsvファイルが出力されない問題

問題

実験用にデータをcsvファイル出力するROSノードを作ったのに、単体(rosrun)ではファイルが作られるのにroslaunchでは作られない。

#これは作られる
rosrun imu imu_node

#これだと作られない
roslaunch imu imu.launch
<?xml version="1.0"?>
<launch>

  <node pkg="imu" type="imu_node" name="imu_node" />

</launch>

ちなみにROSのバージョンはKineticだが、全バージョン共通だと思う。たぶん。

原因と解決法

原因はlaunchファイルのオプションにあった。デフォルトではノードの実行場所は$ROS_HOMEになっていて、これはデフォルトで「~/.ros」になっている。ここを見てみるとちゃんと作られているようだ。

aaa@bbb:~$ ls .ros/
imu.csv                             rospack_cache_10391301543023691066
log                                 rospack_cache_11134725904490598093
roscore-11311.pid                   rospack_cache_15154294043039909020
rosdep                              rospack_cache_16685232255138339616
rospack_cache_04149954776256916034

隠しフォルダになっていると面倒なので、保存場所を変えたいと思って調べてみた。解決方法は3つ。

解決法①

launchファイルにオプションを追加して保存場所を変える。

<?xml version="1.0"?>
<launch>

  <node pkg="imu" type="imu_node" name="imu_node" cwd="node"/>

</launch>

nodeタグの中にオプション「cwd=”node”」を追加すると保存場所がプログラムの場所になる。つまり「~/catkin_ws/devel/lib/imu」になる。余計にわかりづらい。

解決法②

環境変数「ROS_HOME」を変える。デフォルトでは「~/.ros」になっているのを次のようにすると変えることができる。

export ROS_HOME=/home/aaa

そのまま同じターミナルでlaunchファイルを実行すると指定したフォルダにcsvファイルが作られる。しかし問題点が2つあり、ひとつはターミナルを起動する度に毎回上のコマンドを打たなければならないこと(.bashrcに上記を追記すれば大丈夫だけど…)。もうひとつは指定したパスにログファイルも作られてしまうことだ。ホームディレクトリとかにするとlogフォルダが出来ているとなんとなく嫌な感じがする。

解決法③

調べたところ、上2つの方法しか見当たらなかった…。やっぱり一番良いのはノードのソースファイル自体で保存場所をフルパスで指定すること。今までは

std::ofstream fout("imu.csv");

のように書いていたところを

std::ofstream fout("/home/aaa/imu.csv");

にすることで事無きを得た。
launchファイルって意外と融通きかないみたいで悲しい。

ROSのTFとは?簡単に説明(サンプルコード付き)②

前回、2つの座標系間の位置関係が変化しない場合のTFの発信をしました。今回は常に位置関係が変化する時のTFについて説明します。

動的TF

ロボットを扱っていると、常に位置関係が変化する2つの座標系を表したい時がよくあります。例えば部屋から見たロボットの位置だったり、アームロボットの手先の位置を表すのにもTFを使います。今回は変化するTFをどう発信するか説明します。

といっても基本的な考え方は静的TFの時と同じです。まずはサンプルプログラムを載せます。

#include <ros/ros.h>
#include <tf/transform_broadcaster.h>

int main (int argc, char** argv){
  ros::init(argc, argv, "tf_publisher");
  ros::NodeHandle nh;
  ros::Rate loop_rate(10);

  tf::TransformBroadcaster odom_broadcaster;

  double t = 0;

  while(ros::ok()){
    ros::spinOnce();

    geometry_msgs::TransformStamped odom_trans;

    //現在時刻、親フレーム、子フレームの指定
    odom_trans.header.stamp = ros::Time::now();
    odom_trans.header.frame_id = "map";
    odom_trans.child_frame_id = "base_link";

    //親フレームからみた子フレームの位置
    odom_trans.transform.translation.x = t;
    odom_trans.transform.translation.y = sin(3.14*t);
    odom_trans.transform.translation.z = 0.0;
    odom_trans.transform.rotation = tf::createQuaternionMsgFromYaw(cos(3.14*t));

    //TFを一回発信
    odom_broadcaster.sendTransform(odom_trans);

    t += 0.01;

    loop_rate.sleep();
  }
}

順番に見ていくと、まず最初にtransform_broadcasterをインクルードします。

#include <ros/ros.h>
#include <tf/transform_broadcaster.h>

続いてmain関数の最初の4行目でgeometry_msgs::TransformStamped型の変数を作っています。そんでそのヘッダ部分に現在時刻と親フレーム、子フレームの情報を入れます。

geometry_msgs::TransformStamped odom_trans;

//現在時刻、親フレーム、子フレームの指定
odom_trans.header.stamp = ros::Time::now();
odom_trans.header.frame_id = "map";
odom_trans.child_frame_id = "base_link";

そのあとが今回の本題、TFの座標を入れる部分です。x,y,zと回転を入れます。回転はクオタニオン(四元数)で表現します。クオタニオン表現では3次元ベクトルとそのベクトル周りの回転量で姿勢を表しますが、2次元平面だけで考える場合はヨー角からクオタニオンを作ってくれる便利な関数があるので非常に楽です。

//親フレームからみた子フレームの位置
odom_trans.transform.translation.x = t;
odom_trans.transform.translation.y = sin(3.14*t);
odom_trans.transform.translation.z = 0.0;

//ヨー角からクオタニオンを作ってrotationに入れる
odom_trans.transform.rotation = tf::createQuaternionMsgFromYaw(cos(3.14*t));

TFの準備ができたところで次の関数で発信します。

//TFを一回発信
odom_broadcaster.sendTransform(odom_trans);

これを一定時間ごとに繰り返すだけで、動的TFの発信は終わりです。
今回はsin曲線を描きつつ進行方向(=微分方向=cos)を向くようにしたのでこんな感じの軌跡ができるはずです。

発信されたTFをRVizで確認。fixed_frameをmapに変えるのを忘れずに!

ROSを最初から勉強したい方はこちら

ros::Time::now()を数値にしてcsvに書き込もうとしても同じ値が出る件

はじめに

実験とかのためにROSトピックの値をcsvファイルに出力したいことが良くあるんですが、ROSの時刻を出力するのにはまったので解決までのメモです。

問題点

ROSの時間を数値(double)に変換するには

double t = ros::Time::now().toSec();

でOK。そんでcsvファイルに出力する

#include <ros/ros.h>
#include <fstream>

int main(int argc, char** argv){
  ros::init(argc, argv, "record_to_csv");
  ros::NodeHandle nh;
  ros::Rate loop_rate(20);
  
  std::ofstream fout("data.csv");
  fout << "Time[s]" << std::endl;

  while(ros::ok()){

    double t = ros::Time::now().toSec();
    fout << t << std::endl;

    loop_rate.sleep();
  }
}

と思ったら、全部同じ値になってしまった!

Time
0
1529990000
1529990000
1529990000
1529990000
1529990000
1529990000
1529990000
...

慌てて色々試したところ、ROS_ERRORではちゃんと表示される模様。

ROS_INFO("%lf",ros::Time::now().toSec());
[ INFO] [1530608049.263032619]: 0.000000
[ INFO] [1530608049.566322356, 1529991633.616588554]: 1529991633.616589
[ INFO] [1530608049.566409625, 1529991633.616588554]: 1529991633.616589
[ INFO] [1530608049.617069613, 1529991633.667305116]: 1529991633.667305
[ INFO] [1530608049.667780029, 1529991633.717981430]: 1529991633.717981
[ INFO] [1530608049.718522891, 1529991633.768572181]: 1529991633.768572

ところがstd::coutでは出力されない。

std::cout << ros::Time::now().toSec() << std::endl;
0
1.52999e+09
1.52999e+09
1.52999e+09
1.52999e+09
1.52999e+09
1.52999e+09
1.52999e+09

解決

表示がROS_INFOと違うので疑っていたが、やはり桁数の問題だった。ファイル書き込みではデフォルトで6桁以降は切り捨てられるようだ。ROS関係のほうを疑ったり、ros::Time(0)を試してみたりしたけど全然だめで、ros::Time::now().secとros::Time::now().nsecを読んで(どちらもuint型)合計するとかやってしまった…。
std::coutもfoutも次のようにすれば桁数の設定ができる。

fout << std::setprecision(20) << ros::Time::now().toSec() << std::endl;
0
1529991603.1314094067
1529991603.1314094067
1529991603.1819009781
1529991603.2322974205
1529991603.2829723358
1529991603.3336381912
1529991603.3842408657

プログラミング上手い人にとっては常識なのかも知れないけど、そんなこともあるのか。勉強になりました。

ROSのsensor_msgs:PointCloud2データの構造と扱い方

はじめに

研究でデプスセンサを使うことがよくあるのですが、デプスセンサからの送られてくるsensor_msgs::PointCloud2形式のデータの扱いについて困った事がありました。例えばあるxy座標でのz座標を取り出したり、ある範囲のデータだけ取り出したりするのが意外と難しかったので、つまずいたことをまとめておきます。

データ形式

ROSの公式ドキュメントを見ればデータ形式そのものはわかるのですが、意味を理解するのにちょっと時間がかかりました。

まず、ポイントクラウドのデータ本体は「data」にあります。これは8ビット(0~255)の数字の列になっていますが、このデータの読み方を定義しているのが「fields」です。ROSのトピックを見てみると、次のようなデータになっていることがわかります。

rostopic echo /point_cloud/fields
- 
  name: "x"
  offset: 0
  datatype: 7
  count: 1
- 
  name: "y"
  offset: 4
  datatype: 7
  count: 1
- 
  name: "z"
  offset: 8
  datatype: 7
  count: 1
--

fieldsは配列になっていて、各データの見方を表しています。
各fieldの見方について説明すると、

  • [name]:値の名前
  • [offset]:先頭から何番目に始まっているか
  • [datatype]:データ型
  • [count]:何個のデータが入っているか(?)

を表します。つまりデータの入れ方は次のようになります。

data[]のデータ構造

point_stepは1つの点を何個のデータで表すか、で今回は12となります。
さて、xyz各データの読み方ですが、これはfieldのdatatypeで読むことになります。ドキュメントによると今回のデータ型「7」はfloat32を意味するとのことなので、この4バイトをfloat型で読み込めばいいということになります。(4バイトをfloatにする方法はネットに資料が沢山ありますので割愛します。)

data[]とfields[]以外のデータは主にポイントクラウドの大きさなどを表す情報で、

  • [height]:高さ
  • [width]:幅
  • [is_bigendian]:datatypeがビッグエンディアンかどうか
  • [point_step]:1つの点を何個のデータで表すか
  • [row_step]:1行を何個のデータで表すか
  • [is_dense]:すべての値が正常かどうか

などがあります。

ROSの勉強…

ラズパイで(ホスト名).localでSSH接続...できない(できた)

 

ホスト名の変更

ラズパイは「ssh (ユーザー名)@(ホスト名).local」でIPアドレスを覚えなくても接続できてこれがかなり便利です。デフォルトだと「ssh pi@raspberrypi.local」です。

ところが2つ以上のラズパイを接続したい場合だと、デフォルトのままではホスト名が重複してしまうので、どちらか片方のホスト名を変更する必要があります。

sudo raspi-config

で設定を開き、「2 Network Options」→「N1 Hostname」を選択して変更するだけ!

ところが再起動後に「ssh pi@raspi_zero.local」とやっても接続できませんでした。

理由はホスト名に_(アンダースコア)を使っていたこと。代わりに-(ハイフン)を使うようにしたら問題なく接続できました!
意外な落とし穴でしたが、みなさん気をつけましょう。

I2C 16×2 LCDで日本語(カタカナ)を表示する(pythonプログラムあり)

LCDでカタカナ表示

こんにちは。ラズパイやArduinoでセンサ等を利用していると、気温などの数値をどうやって表示するか意外に悩みます。そんなメッセージや数字を表示したい時に便利なのがLCDディスプレイです。LCDは電光掲示板のようにドットで表現された文字を表示するデバイスで、ラズパイの5V電源でも動かすことが出来ます。

Osoyoo製LCD。電子工作入門セットに入っていました。

上の電子工作入門セットに入っていたOsoyoo製LCDは、I2Cで簡単にラズパイと接続できて、さらに制御用pythonプログラムも公式で公開されているのでとっても簡単にメッセージが表示できる…、のですがこの制御プログラム、英数字の出力のみでカタカナの表示方法が全く説明されていません。ドキュメントは日本語なのに不親切極まりないです。(初心者向けセットなのにマニュアル等一切なし。せめて各文字に対応するアドレスだけでも公開して欲しい…)

というわけで今回はこれにカタカナや記号を表示させることを目標にしたいと思います。

(追記)
このLCDの裏にはんだ付けされているI2C接続用マイコン「HD44780」は有名らしく、こちらで検索するとわりとヒットしました。今回はOsoyooのプログラムをもとにpythonで操作する方法をご紹介します。

このICは広く利用されているようで、説明されているページも多くありました。

表示は出来るっぽい

Osoyooの公式ページに従って進めてみます。

とりあえずドキュメントに従ってLCD制御用pythonプログラムをダウンロードして、文字を表示させてみる。LCDとラズパイの5V、OUT、SDA(#2ピン)、SDL(#3ピン)を接続して、制御プログラムのmain関数の中に次のように書けば英数字は表示できます。

def main():
  lcd_init()
  lcd_string("Hello world!",LCD_LINE_1)

このままだと表示されたメッセージが一瞬で消えてしまうので、一番下の二行をコメントアウトしておきます。

#finally:
  #lcd_byte(0x01,LCD_CMD)

これで英数字は問題ないのですが、カタカナを表示しようとすると文字化けしてしまいます。

対策

かなり強引ですが、文字化けすることを見越して元のテキストを変えてしまう変換表を作ってみました。表示したいテキストを一回この関数で変換した後、上記の方法でとりあえず正しい文字が表示できます。

def katakana(text):
	list1a = u"「。、」ヲ・ィァェゥォャュョッーアイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワン゛゜"
	list1b = u"アァイィウゥエェオォカガキギクグケゲコゴサザシジスズセゼソゾタダチヂッツヅテデトドナニヌネノハバパヒビピフブプヘベペホボポマミ"
	list2a = u"ガギグゲゴザジズゼゾダヂヅデドバビブベボ"
	list2b = u"ザシジスズセゼソゾタダチヂッツナニヌネノ"
	list3a = u"パピプペポ"
	list3b = u"ナニヌネノ"
	
	text_changed = ""
	for l in text:
		#カタカナを変換
		if list1a.find(l) >= 0:
			text_changed += list1b[list1a.find(l)]
		#濁音を清音+濁点に
		elif list2a.find(l) >= 0:
			text_changed += list2b[list2a.find(l)]
			text_changed += u"マ"
		#半濁音を清音+半濁点に
		elif list3a.find(l) >= 0:
			text_changed += list3b[list3a.find(l)]
			text_changed += u"ミ"
		#その他の文字はそのままにする
		else:
			text_changed += l
	
	return text_changed
def main():
  lcd_init()
  text = u"ラズベリーパイ"
 text2 = katakana(text)
  lcd_string(text2,LCD_LINE_1)

対策②

上の方法は対処療法であまりよろしくないので、いっそ表示出来る文字を全て調べてみました。制御プログラムを見たところによると、文字の表示は「0x80 0x##」を送ることで出来るみたいです。「##」の部分に対応する文字を全て表示させてみた結果はこのようになりました。

ほぼShift-JISっぽいが少し違う所も…

どうしてもPCで出せなかった文字は説明にしてあります。英数字やカタカナはShift-JISに沿っていますが、「千」「万」「円」「√」などの文字もあります。これだけあれば色んな情報が表示出来そうですね。個人的には「℃」が使いたいのですが、「゜」「C」で表示出来そうです。

これらの文字を出すにはmain関数内に次のように書きます。

def main():
  lcd_init()
  
  #何行目に表示するか指定
  lcd_byte(LCD_LINE_1,LCD_CMD)
  
  #0xFC=「円」を表示
 lcd_byte(0xFC,LCD_CHR)

「円」を表示することができたでしょうか?
これだと一文字しか表示できず、また毎回表を見るのも面倒なので文字コードの順に並べたリストを作ってみました。文字コードnの文字がn番目に出てくるようにしてあります。これでカタカナを含む文字列を上の表の文字コードに変換して表示出来ます。

2019/11/3追記: 表示される文字のリストcodesの0x27のシングルクォーテーションが抜けていましたので修正しました。

def lcd_string_kana(message,line):
	#文字コードnの文字 = n番目の文字
	codes = u'線線線線線線線線線線線線線線線線                !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}→←                                 。「」、・ヲァィゥェォャュョッーアイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワン゛゜αäβεμσρq√陰ι×¢£nöpqθ∞ΩüΣπxν千万円÷ 塗'
	#変換する文字の辞書(ex.「バ」=「ハ」「゛」)
	dic ={u'ガ':u'カ゛',u'ギ':u'キ゛',u'グ':u'ク゛',u'ゲ':u'ケ゛',u'ゴ':u'コ゛',u'ザ':u'サ゛',u'ジ':u'シ゛',u'ズ':u'ス゛',u'ゼ':u'セ゛',u'ゾ':u'ソ゛',u'ダ':u'タ゛',u'ヂ':u'チ゛',u'ヅ':u'ツ゛',u'デ':u'テ゛',u'ド':u'ト゛',u'バ':u'ハ゛',u'ビ':u'ヒ゛',u'ブ':u'フ゛',u'ベ':u'ヘ゛',u'ボ':u'ホ゛',u'パ':u'ハ゜',u'ピ':u'ヒ゜',u'プ':u'フ゜',u'ペ':u'ヘ゜',u'ポ':u'ホ゜',u'℃':u'゜C'}
	message = message.ljust(LCD_WIDTH," ")
	lcd_byte(line, LCD_CMD)
	
	#濁音など、文字の変換
	message2 = ""
	for i in range(LCD_WIDTH):
		if ( message[i] in dic.keys() ):
			message2 += dic[message[i]]
		else:
			message2 += message[i]
	
	#文字コードのリストにある文字なら表示、それ以外は表示しない
	for i in range(LCD_WIDTH):
		if (codes.find(message2[i]) >= 0):
			lcd_byte(codes.find(message2[i])+1,LCD_CHR)
		elif (message2[i] != u' '):
			print "No such character!    :" + message2[i]

ついでに「ガ」などLCDでは二文字で表示する文字を「カ」「゛」に分解する処理も混ぜておきました。

全体のプログラムも載せておきますご参考までにどうぞ。

#coding:utf-8
import smbus
import time
import sys

# Define some device parameters
I2C_ADDR  = 0x3f # I2C device address, if any error, change this address to 0x3f
LCD_WIDTH = 16   # Maximum characters per line

# Define some device constants
LCD_CHR = 1 # Mode - Sending data
LCD_CMD = 0 # Mode - Sending command

LCD_LINE_1 = 0x80 # LCD RAM address for the 1st line
LCD_LINE_2 = 0xC0 # LCD RAM address for the 2nd line
LCD_LINE_3 = 0x94 # LCD RAM address for the 3rd line
LCD_LINE_4 = 0xD4 # LCD RAM address for the 4th line

LCD_BACKLIGHT  = 0x08  # On
#LCD_BACKLIGHT = 0x00  # Off

ENABLE = 0b00000100 # Enable bit

# Timing constants
E_PULSE = 0.0005
E_DELAY = 0.0005

#Open I2C interface
#bus = smbus.SMBus(0)  # Rev 1 Pi uses 0
bus = smbus.SMBus(1) # Rev 2 Pi uses 1

def lcd_init():
  # Initialise display
  lcd_byte(0x33,LCD_CMD) # 110011 Initialise
  lcd_byte(0x32,LCD_CMD) # 110010 Initialise
  lcd_byte(0x06,LCD_CMD) # 000110 Cursor move direction
  lcd_byte(0x0C,LCD_CMD) # 001100 Display On,Cursor Off, Blink Off 
  lcd_byte(0x28,LCD_CMD) # 101000 Data length, number of lines, font size
  lcd_byte(0x01,LCD_CMD) # 000001 Clear display
  time.sleep(E_DELAY)

def lcd_byte(bits, mode):
  # Send byte to data pins
  # bits = the data
  # mode = 1 for data
  #        0 for command

  bits_high = mode | (bits & 0xF0) | LCD_BACKLIGHT
  bits_low = mode | ((bits<<4) & 0xF0) | LCD_BACKLIGHT
  
  # High bits
  bus.write_byte(I2C_ADDR, bits_high)
  lcd_toggle_enable(bits_high)

  # Low bits
  bus.write_byte(I2C_ADDR, bits_low)
  lcd_toggle_enable(bits_low)

def lcd_toggle_enable(bits):
  # Toggle enable
  time.sleep(E_DELAY)
  bus.write_byte(I2C_ADDR, (bits | ENABLE))
  time.sleep(E_PULSE)
  bus.write_byte(I2C_ADDR,(bits & ~ENABLE))
  time.sleep(E_DELAY)

def lcd_string(message,line):
  # Send string to display

  message = message.ljust(LCD_WIDTH," ")

  lcd_byte(line, LCD_CMD)

  for i in range(LCD_WIDTH):
    lcd_byte(ord(message[i]),LCD_CHR)

def lcd_string_kana(message,line):
	#文字コードnの文字 = n番目の文字
	codes = u'線線線線線線線線線線線線線線線線                !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}→←                                 。「」、・ヲァィゥェォャュョッーアイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワン゛゜αäβεμσρq√陰ι×¢£nöpqθ∞ΩüΣπxν千万円÷ 塗'
	#変換する文字の辞書(ex.「バ」=「ハ」「゛」)
	dic ={u'ガ':u'カ゛',u'ギ':u'キ゛',u'グ':u'ク゛',u'ゲ':u'ケ゛',u'ゴ':u'コ゛',u'ザ':u'サ゛',u'ジ':u'シ゛',u'ズ':u'ス゛',u'ゼ':u'セ゛',u'ゾ':u'ソ゛',u'ダ':u'タ゛',u'ヂ':u'チ゛',u'ヅ':u'ツ゛',u'デ':u'テ゛',u'ド':u'ト゛',u'バ':u'ハ゛',u'ビ':u'ヒ゛',u'ブ':u'フ゛',u'ベ':u'ヘ゛',u'ボ':u'ホ゛',u'パ':u'ハ゜',u'ピ':u'ヒ゜',u'プ':u'フ゜',u'ペ':u'ヘ゜',u'ポ':u'ホ゜',u'℃':u'゜C'}
	message = message.ljust(LCD_WIDTH," ")
	lcd_byte(line, LCD_CMD)
	
	#濁音など、文字の変換
	message2 = ""
	for i in range(LCD_WIDTH):
		if ( message[i] in dic.keys() ):
			message2 += dic[message[i]]
		else:
			message2 += message[i]
	
	#文字コードのリストにある文字なら表示、それ以外は表示しない
	for i in range(LCD_WIDTH):
		if (codes.find(message2[i]) >= 0):
			lcd_byte(codes.find(message2[i])+1,LCD_CHR)
		elif (message2[i] != u' '):
			print "No such character!    :" + message2[i]

def main():
  # Main program block

  # Initialise display
  lcd_init()
  argv = sys.argv
  argc = len(argv)
  if (argc == 2):
  	lcd_string(katakana(argv[1].decode('utf-8')),LCD_LINE_1)
  elif (argc == 3):
  	lcd_string(katakana(argv[1].decode('utf-8')),LCD_LINE_1)
  	lcd_string(katakana(argv[2].decode('utf-8')),LCD_LINE_2)
  else:
  	#lcd_string(katakana(u"No data."),LCD_LINE_1)
  	lcd_string_kana(u"キオン:∞℃です",LCD_LINE_1)
  	lcd_string_kana(u"チョキン:1千万円",LCD_LINE_2)

if __name__ == '__main__':

  try:
    main()
  except KeyboardInterrupt:
    pass
  #finally:
    #lcd_byte(0x01, LCD_CMD)

カタカナや記号を含めたメッセージを表示することに成功しました!
色々表示させて遊んでみます。

データの表示、ユーザーインターフェースなど色々使えそう

rosbagの便利オプション

実験時のセンサデータとかをすべて記録しておけるrosbagはROSの便利機能の中でもかなり有用な機能です。センサが手元に無くても記録したデータを再生することで、システムがちゃんと動作するか確認することが出来ます。
一方でrosbagには独自の使い方があったりするので、ここで一度出来ること&出来ないことを確認してみたいと思います。

事前に今流れているトピックを確認する

rostopic list

#例
rostopic list | grep cmd #「cmd」を含むものだけ表示

まず最初に記録すべきトピック、記録しなくてもいいトピックを見極めましょう。grepによる検索と合わせて使うことも多いです

record関連

特定のトピックだけrecord

rosbag record (トピック名) (トピック名) ...

#例
rosbag record /joy /cmd_vel
rosbag record -O input_data /joy #「input_data.bag」ファイルに「/joy」トピックを記録

特定のトピックだけを狙って記録する方法です。すべてのデータを記録するとファイルサイズがGB単位になって大変な時はこのやり方もありです。
しかし、あとになってこのデータ取っておけば良かった、となること多しなので慎重に決めましょう。

すべてのトピックをrecord

rosbag record -a

すべてのトピックを記録する方法です。後で完全にシミュレート出来ますが、ファイルサイズが膨大になります。小さなシステムならむしろおすすめです。再生したときにTFが上手く表示されない時は、下のplay –clockを使いましょう。

正規表現にマッチするトピックだけrecord

rosbag record -e "(正規表現)"

#例

rosbag record -e "/MyRobot/.*" #「/MyRobot/」で始まるトピックのみ記録する
rosbag record -e ".*(joy|lidar|imu).*"  #「joy」「lidar」「imu」のいずれかを含むトピックのみ記録する

〇〇で始まるトピック等、一部のトピックだけ記録したいときや、センサからのメッセージだけ記録したいときによく使います。

正規表現にマッチするトピックだけ除外する

rosbag record -x "(正規表現)"

#例
rosbag record -a -x ".*robot.*" #「robot」を含むトピックを除外する
rosbag record -a -x ".*(image|velodyne).*" #「image」または「velodyne」を含むトピックを除外する

正規表現にマッチしたトピックだけを除外することができます。必ず-aオプションか-eオプションと共に使う必要があります。データが大きすぎてrecordするとbagファイルの容量が大きくなってしまうときに有効です。

play関連

ループ再生

rosbag play test.bag -l
# または
rosbag play test.bag --loop

bagファイルをリピート再生します。センサデータを使ってシステムのチェックを行いたい時等、何回も再生しなおすのが面倒な時に便利です。

ちなみに通常の方法でも同様ですが、再生中にスペースキーを押すとポーズが出来ます。さらに、ポーズ中に「s」キーを押すと0.1秒ずつ再生をすすめることができます。

bagファイルのクロックを使用

rosparam set /use_sim_time true #roscoreから出力されるクロックを停止
rosbag play test.bag --clock #bagファイルのクロックを出力

通常bagファイルを再生するときはroscoreのクロック(現在の時間)を使用しますが、時間が重要な場合(TFなど)はbagファイル記録時の時間を使うほうが良いです。一行目でroscoreのクロックを無効にし、二行目でバグファイルの時間を基準の時間として再生します。

倍速・スロー再生

rosbag play -r 0.5 test.bag

データを0.5倍速で再生します。データをゆっくり見たいときに。

途中から再生

rosbag play -s 45 -u 10 test.bag

45秒からスタートして10秒間再生します。一部だけ集中して見たい時や途中から見たいときに使います。-rや-lと合わせて使うことも多いです。

トピック名をリネームして再生

rosbag play test.bag tf:=tf_old

tfという名前のトピックをtf_oldにリネームして再生します。同じ名前のトピックがあってエラーが起きるときに使います。

検証関連

bagファイルの情報を表示

rosbag info test.bag

バグファイルにどんなトピックが記録されているか等の情報を見ることが出来ます。

ROS初心者の方はまずはこちらの本がオススメです。

catkin_make時の便利なコンパイルオプション

はじめに

ROSプログラムをコンパイルする時はcatkin_makeを使っているけれど、仕組みはいまいち分かっていない。(そもそもmakeって何かも良くわかっていない)

そんなあなたも取り敢えず試せる便利なコンパイルオプションがこちら。

msg.hが無いと言われる他

ちゃんとCMakelist.txtに書いてるのに作られてない…

これはおそらくメッセージを作るより前にコンパイルエラーで止まってるのが原因。次のオプションで、エラーが起きたプログラムはとりあえず無視して最後までコンパイルできる。

catkin_make -i

もう一度コンパイルするとエラー出ない。

warningめっちゃ出るんだけど…

前はコンパイル出来たやつがあるときからめっちゃwarning出ることがある。またはerror[warning]みたいになって止まる。

そんなとき、エラーを全無視するオプションがこちら。

catkin_make --cmake-args -DCMAKE_CXX_FLAGS="-w"

エラーを放置することになるのであんまりよくないかも。

パッケージを除外する

他の人が作ったパッケージは動かないことが往々にしてある。その人が帰ってくるまで待つとして、その間そのパッケージは無視してコンパイルしたい時は

catkin_make -DCATKIN_BLACKLIST_PACKAGES="package1;package2"

温度・湿度・気圧が全部測れるセンサーGAOHOU製GY-BME280の使い方

はじめに

温湿度計を作ろうと思って調べていたら、こちらのセンサーにたどり着きました。

4ピン温湿度+気圧測定モジュール

 

2週間かかりましたが無事到着。使い方を調べようと思ったのですが、他の似たようなモジュールの方が有名みたいでこの製品について説明しているページが皆無…そんなもん先に調べとけ
ということで実際に使ってみてわかったことを紹介します。

このページでは4ピンのモジュール「GY-BMEP」について説明します。
6ピンのBME280モジュールでは無いので注意して下さい!

何ができる

  • 温度・湿度・気圧がわりと良い精度で測定できる
  • I2C接続
  • アドレスは0x76または0x77が選べる
  • 読み出しは早い

センサーの精度はわりといい感じです。この辺はずっと使ってみないとわかりませんが。

Vin、GND、SCL、SDAの4つのピンがあるので、I2Cで接続します。Arduino Nanoを使う場合は
SCL→A5ピン
SDA→A4ピン
に接続します。

電源は6ピンのモジュールと違って3.3Vでも5Vでも使えるみたいです。非常に便利です。
I2C通信を行うスケッチはこちらのページ丸パクリ参考にさせて頂きました!

※説明欄ではSPIでも通信できると謳っていますが出来ませんでした。
チップ自体は対応していますが、接続するピンがありません

真ん中の3つ端子はなに?

日本語で探しても答えにたどり着けず、モジュール名で検索して英語のQ&Aを見てみたら回答がありました。

どうやらI2C通信のデバイスアドレスを選択するためのものだそうです。
1つしか使っていないときは問題になりませんが、2つ以上同じ機器を使う時は、同じアドレスが2つ存在しないようにアドレスを変更します。このデバイスは通常時は0x76ですが、次のようにすると0x77に変更できる、とあります。

金の端子の真ん中と左の間を鋭利なナイフ等で傷つけて断線し、右と真ん中をハンダ付けして導通させます。

繊細な作業が必要そう

外気と室内の両方の気温が知りたい時とかに使えそうですね

 

今回は以上です
質問等ありましたら下のフォームからどうぞ

モータードライバ L9110でモーターを逆回転させる

はじめに

カーテンを自動で開閉する仕組みを作りたかったときに、モータを逆回転したいなと思ったことがありました。プログラムを変えるとか簡単な回路で済むかと思ったら、意外と難しそうだったのでモジュールを使うことにしました。今回はIC L9110およびそれを使ったモジュールの使い方のメモです。

モジュール

今回使うのはこのモジュールです。
Amazonでなんと1個150円!思ったより小さくていい感じです。

サイズは10円玉大

ちなみに、1つのモジュールで2つのモーターを別々に動かせます。
電流は各800mAまでだそうです。

ピンは6つあって、Vcc、GND、Aの入力2つ、Bの入力2つです。緑の部分にモーター線をネジ止めします。入力のどちらか片方を0Vに落とすとモーターが正回転or逆回転します。

うーん、便利!
こういう保護回路とか全部ついた簡単モジュールはいいですね。

データシート

L9110のデータシートの意訳です。

  • 電源電圧:2.5Vから12V
  • 許容電流(定常):800mA×2個
  • 許容電流(瞬間):2.0A
  • 無負荷時電流:2μA
  • 制御電流:0.5mA
  • TTL/CMOSレベルに対応
  • 誘導電流、高電圧に対する保護回路付き

入出力レベル

入力1入力2出力1出力2
HLHL
LHLH
LLLL
HHLL

※以下、実際に使ってみてわかった注意点

  • 出力電圧は電源電圧の80%くらいになる
  • 制御入力は何も接続されていないときはHighレベルになる(内部プルアップ)