更新日: 2024年10月1日


I. 人生の余技として作成したプログラム(4)

9. C++

駄待ち狐は11月で定年を迎えるという2017年に、ロボットを事業化するSBU(strategic/small business unit)に異動しました。某コンビニ向けのレジロボと某配送会社向けの搬送ロボが2枚看板です。自ら開発した光通信用受光素子も、特許面から支援した多層膜カラーフィルタも、技術戦略の名の下で売込みに奔走した他人の開発品も、ことごとく事業化できなかったので、会社人生のロスタイム(定年後再雇用)で最後の挑戦をしたかったのです。このSBUを立上げたのは駄待ち狐の元部下で、企画・構想段階から相談を受けていました。しかし、哀れ再雇用の身、その元部下のSBU長から「特許を見て下さい」と言われました。

2019年にはSBUが解体(社内用語では発展的解消)されて搬送ロボの部隊に入ったので、この年は日米欧中の搬送用ロボットの特許をただひたすら調査していました。一方、事業化の方は某配送会社との蜜月が終り、結局は導入してもらえないということになっていました。そこで2020年はまた振出しに戻って、どうやって事業化するかという議論の蒸返しです。さすがにこれでは駄目だと思い、2020年の12月から搬送ロボを動かしているROS(ロボットOS)の勉強を始めることにしました。ただ、システム開発が本職になった訳ではなく、コロナ禍の在宅勤務をいいことに、戦力外として勝手に勉強していただけです。

また教科書を買ったのですが、ROSはコンピュータのOSではなく既存のOS(Linuxが主流)上で動くミドルウエアで、プログラムそのものはC++あるいはPythonで記述されます。C++にもPythonにも縁はなかったので、C++を使ってROSのプログラミングをする本と、Pythonの入門書の2冊を買いました。個人所有のレッツノートSZ6は別の目的でWindows-Ubuntuデュアルブートにしてあったので、ハードウエア面は心配ありません。本当はロボットも欲しいところですが、ROSにはロボットシミュレータのパッケージがあり、これで我慢します。

ROSとC++は初学者には難解で、教科書に書いてある通りにやればその通りになるのですが、なぜそうするのかはよく分かりません。また、ROSには膨大なパッケージ群があり、複雑な依存関係になっています。このパッケージをソースコードから自分でビルドすることも可能ですが、そのソースコードはさらに難解です。ただ、自分でプログラミングをしてみないと始まらないと思い、最初に書いたコードが以下のものです。これはロボットに対して目標姿勢として座標(1.7, -1.0)でy軸の負の向きを指定し、そこに到着すると出発地点である座標(-2.0, -0.8)でx軸の正の向きを新たな目標姿勢にするということを繰返します。

コードの表示にはPrism.jsを使用しています。
prism.cssの設定は団塊爺ちゃんの備忘録を参考にしました。

#include <ros/ros.h>
#include <geometry_msgs/PoseStamped.h>
#include <move_base_msgs/MoveBaseActionResult.h>

bool fwd_flg = true;

void mbrCb(const move_base_msgs::MoveBaseActionResult::ConstPtr& result){
	if(result->status.status == 3){
		fwd_flg = !fwd_flg;
	}
}

int main(int argc, char **argv){
	ros::init(argc, argv, "goal_navi");
	ros::NodeHandle nh;

	ros::Subscriber sub = nh.subscribe("move_base/result", 1, mbrCb);
	ros::Publisher pub = nh.advertise<geometry_msgs::PoseStamped>("move_base_simple/goal", 1);

	ros::Rate loop_rate(1);     
	ros::Time time = ros::Time::now();

	geometry_msgs::PoseStamped goal_point;
	goal_point.pose.position.z = 0.0;
	goal_point.pose.orientation.x = 0.0;
	goal_point.pose.orientation.y = 0.0;
	goal_point.header.stamp = time;
	goal_point.header.frame_id = "map";

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

		if(fwd_flg){
			goal_point.pose.position.x = 1.7;
			goal_point.pose.position.y = -1.0;
			goal_point.pose.orientation.z = -sqrt(2) / 2;
			goal_point.pose.orientation.w = sqrt(2) / 2;
		}else{
			goal_point.pose.position.x = -2.0;
			goal_point.pose.position.y = -0.8;
			goal_point.pose.orientation.z = 0.0;
			goal_point.pose.orientation.w = 1.0;
		}
		pub.publish(goal_point);
		loop_rate.sleep();
	}
	return 0;
}

C++はCをベースにしたオブジェクト指向言語で、コードの煩雑さが全部載せになっています。型宣言は変数の前に書くだけでなく"<>"の中に書く場合もあり、名前空間の連結"::"があり、ポインタ"*"と参照"&"の他にオブジェクトポインタ"->"とオブジェクトの参照"."があります。これに加えてROS固有のpublisher(18行目)とsubscriber(17行目)があり、繰返しあるいは1周期分実行するspin()/spinOnce()があり、姿勢の向きを指定するクォータニオン(36~37行目)があります。以上を理解すれば上記の短いプログラムは解読できますが、もっと複雑怪奇なコードと格闘した経緯は「VIII. ROSによるAMRのシミュレーション」で述べます。

10. Python

C++はROSのためだけに学習したのですが、Pythonの方は駄待ち狐として押えておくべきこれからのプログラミング言語という認識でした。そこで、専門学校生が使うとおぼしき「初めて学ぶ言語がPython」でも理解できるように書かれた教科書でまず勉強しました。この教科書は、今はやりの(出版当時は存在しなかった)生成AIの基本となるマルコフ連鎖による文章生成や、出版当時に全盛だった深層学習による画像認識をコーディングできるところまで話が及んでおり、なかなかのスグレものです。

この教科書を読み進めて最初に戸惑ったのが、"for count in range(10):"という構文です。「これで10回の繰返しってどういうこと?」と思ったのですが、その後にrange(10)はrange(start, stop, step)というオブジェクトでstartとstepを省略したものだと知り、合点がいきました。Perlの「色んな書き方がある方が楽しいよね。自由だし、知らない人に自慢できるし」という思想に対して、Pythonは「誰が書いても同じ書き方になる方が他人が理解しやすいでしょ」という思想です。ですから、forループもforeachだけにして、range()を導入することでfor($count = 0; $count < 10; $count++)を逆に簡略化したのです。

上述のC++のコードをPythonに焼直すと以下のようになります。ROSとしての表記が残るのは致し方ないとして、C++ゆえのややこしさは全て解消されています。因みに、Pythonに型宣言がないのは「面倒くさいから」ではなく、「全ての変数は実体のあるオブジェクトへの参照なので、型を宣言する必要がない」のです。Python以前の言語では変数を宣言すると同時にメモリ上の領域を確保するという考え方ですが、Pythonではメモリ上にある実体に対してその名前として変数名を付けるのです。この結果として、C++をPythonに焼直すのは簡単ですが、その逆は労力を要します。


#!/usr/bin/env python

import math
import rospy
from geometry_msgs.msg import PoseStamped
from move_base_msgs.msg import MoveBaseActionResult

def mbrCb(result):
	global fwd_flg
	if(result.status.status == 3):
		fwd_flg = not fwd_flg

if __name__ == '__main__':
	fwd_flg = True

	rospy.init_node('goal_navi')
	rate = rospy.Rate(1)

	sub = rospy.Subscriber('move_base/result', MoveBaseActionResult, mbrCb)
	pub = rospy.Publisher('move_base_simple/goal', PoseStamped, queue_size=10)

	goalMsg = PoseStamped()
	goalMsg.header.frame_id = 'map'
	goalMsg.header.stamp = rospy.Time.now()
	goalMsg.pose.position.z = 0.0
	goalMsg.pose.orientation.x = 0.0
	goalMsg.pose.orientation.y = 0.0

	while not rospy.is_shutdown():
		if(fwd_flg):
			goalMsg.pose.position.x = 1.7
			goalMsg.pose.position.y = -1.0
			goalMsg.pose.orientation.z = -math.sqrt(2) / 2
			goalMsg.pose.orientation.w =  math.sqrt(2) / 2
		else:
			goalMsg.pose.position.x = -2.0
			goalMsg.pose.position.y = -0.8
			goalMsg.pose.orientation.z = 0.0
			goalMsg.pose.orientation.w = 1.0

		pub.publish(goalMsg)
		rate.sleep()

ROSのプログラミングでは、オリジナルのものはPythonで作成していますが、パッケージ内のソースコードをいじる時はC++のままにしています。C++からPythonにするのは簡単だと述べましたが、何百行もあるプログラムがたくさん複雑な依存関係で絡み合っているものを移植するとなると話は別だからです。年表のPythonの欄にある丸印「画像深層学習」と「DQNナビ」はROSに関するものなので「VIII. ROSによるAMRのシミュレーション」で詳細を述べます。真ん中の丸印は「垂直共振器モード解析」で、FORTRANからCを経て移植したものです。


駄待ち狐がその人生で使ってきたプログラミング言語について紹介してきました。この中で1つだけを選べと言われればPythonになります。"The newer, the better."です。プログラミング言語は開発された当時のハードウエアの状況を反映しています。Pythonをメインフレームで動かすことは無理ですし、FORTRANやCを最新のハードウエアで動かすと無駄な作法が入ってしまいます。ハードウエアとOSが車の両輪として発展して行くのに合せて、プログラミング言語も進化していくものだと思います。ただ、ライブラリやパッケージのことを考えると、OSほど短い周期でアップデートすることはできません。

それに加えて、それぞれの言語の持ち味があると思います。Officeのマクロを作るのであればVBAの一択になるでしょうし、HTMLの表示内容をプログラミングしたいのであればPHP、HTMLの中にプログラムを埋込みたいのであればJavaScriptということになります。そういう意味では「みんなちがって、みんないい」なのですが、世の中に流布させるためには普遍性も必要です。そういった諸々のことを踏まえて、「自作プログラムの50年史」の年表の用途欄を色分けしてあります。クリーム色は「まだまだ頑張って下さい」、グレーは「お疲れさまでした」です。

ここまで駄待ち狐のプログラミング言語史観(私感)をお読みいただき、ありがとうございました。もう一度読返したい方は以下のリンクからどうぞ・・・

  1. 人生の余技として作成したプログラム(1)
    1. FORTRAN
    2. BASIC
    3. C
  2. 人生の余技として作成したプログラム(2)
    4. Perl
    5. Java
    6. JavaScript
  3. 人生の余技として作成したプログラム(3)
    7-1. VBA
    7-2. VBScript
    8. PHP
  4. 人生の余技として作成したプログラム(4)
    9. C++
    10. Python