ROS食わず嫌い症候群患者の悪あがき
これは2024年AdventCalendarの記事です.
しょうもない記事だけど12/25の枠を奪いました
はじめに
全国の逆張り界隈の方なら分かると思いますが、ROSを使うと負けた気になりますよね。
SRAMや画像処理なんかを簡単に実装できてロボット全体を一貫したシステムで実装できる。
そんなことはわかっています、わかっていますが、 SLAMは自分で実装したいし、
ROSの起動時間は遅い。(苦しい言い訳)
そんなわけでこの長い冬休みの間、ぎり代替できなくもないアプリを作って見ました。
実装環境
Ubuntu 24.04.1 LTS
gcc 13.3.0
gnu readline 8.2
仕様を考える
代替のアプリを作ると決めて、どのような仕様にするかを考えました。
まず、全体像としてはROSのように複数の実行ファイルをノードとして管理し、
各ノードをインターフェイスでつなぎ、相互通信するシステムを作ることにしました。
この際、 matlabのような形のインターフェイスにすることで、
PIDなどのプログラムを数式ノードの組み合わせで、数式モデルの形そのままで実装できると考えてインターフェイスなどを調整しました。
当初、これらのシステムをmatlabの操作感で実行できるようにしたいと考え、
GUIで実装することを考えたのですが、開発コストが上がることと、CUIで作成した後に
PythonでCUIを変わりに操作するGUIアプリを作成することで開発コストを下げ、同様のアプリを作成できると考え、CUIで作成することにしました。
CUIを作る上でGNU Readlineが神でした。
できたもの
ここから延々と苦労を語ってもいいのですが興味のない方もいると思うので、完成物を見せた後、苦労を語っていこうと思います。
ちなみにGithubで公開してるけど、まだドキュメントを書けていないので、使う場合は頑張って探りながらになります。(近いうちに書く予定ではある)
完成物はscriptTreeという名前の実行ファイルで、実行するとバージョンを吐き出し、lunchを引数で渡すとシステムが起動します。
$ scriptTree
scriptTree version 1.0.0
$ scriptTree lunch
lunch success.
>>> (ここにコマンドを打っていく)
起動したシステムは GNU readline を使っていて、CUIで操作します。
実装したコマンドは以下の通りです。
説明していない概念がしれっとありますが一旦スルーしてください
コマンド | 説明 |
---|---|
help | 各コマンドの説明が表示される |
quit | システムを終了する |
save | 引数にファイルのパスを渡すと そのファイルに現在起動しているノードを保存する |
load | 引数にファイルのパスを渡すと そのファイルからノードを読み取り起動する |
run | 引数に実行ファイルのパスを渡すとノードとして実行してくれる -name <名前>で名前をつけれる。デフォルトは実行ファイル名 |
connect | <入力ノード> <入力パイプ> <出力ノード> <出力パイプ> の順に 引数を渡すとパイプを接続できる |
disconnect | <入力ノード> <入力パイプ>の順に入力を渡すとパイプを切断する |
list | 各ノードの情報を出力する |
clear | ターミナルをクリアする |
const set | <入力ノード> <コンストパイプ> <数字> でコンストパイプの値を設定する |
const get | <入力ノード> <コンストパイプ> でコンストパイプの値を取得する |
timer run | ノード管理システムのwakeupターマーを開始する |
timer stop | ノード管理システムのwakeupターマーを停止する |
timer set | <数字> でwakeupタイマーの間隔を変更する |
timer get | 現在のwakeupタイマーの間隔を取得する |
さて、長くなってしまいましたが、最後に数学ノードを使い過渡解析をやってみようと思います。なお出力はcsv出力ノードを用意しcsvをPythonでグラフ出力しています。
過渡解析する回路は次の回路です
これをscriptTree上で構築しkaiseki.binというファイルに保存しました。
構築する過程は長いので飛ばしますが、R=1[Ω] C=1000[uF]で作成しました
その後以下のコマンドでkaiseki.binを読み込み実行しました。
$ scriptTree lunch
lunch success.
>>> load kaiseki.bin
~~~長めのログ~~~
~~~~~~~~~~~~~~~
>>> timer set 0.01
timer period set to 0.010000ms
>>> timer run
timer start
>>> quit
左が実行結果のグラフ、右がmatlabで実行したときのグラフになります
開始時間と単位が違うので少し見づらいですがほぼ同じ形のグラフが出力されていることが分かると思います。
さいごに
ここまでお付き合いいただきありがとうございました。
このプログラムはちまちまバージョンアップしていく予定なのでぜひ見守ってください。
リポジトリは以下の4つですこの記事の投稿時ではまだドキュメントがないです!
いつかはSLAMノードとかも実装したい...
-
scriptTree
- このシステム本体
-
nodeSystem
- 各ノードのためのライブラリ
-
nodeExample
- nodeSystemの使い方のExample
-
mathNodes
- 数学ノード
おまけ
さて、この記事が投稿されたのは1/13なわけですが、遅れたのには理由があります。
一つはこのカレンダーに気づいたのが12月の終わりだったこと、
そしてこのプログラムにだいぶ苦労したことです。(まじで大変だった)
そんなわけで、設計方針を解説しつつ苦労を語ろうと思います。
まず、このノード間はパイプでつながっていて、パイプには IN , OUT , CONST の三種類があります。IN と OUT は接続することができ、データの流れは一方向です。CONST は接続ができ、値はコマンドのみで操作できます。
パイプという名前ですが、OS標準機能のpipeではなくshareMemoryを使って実装しています。この仕様が後になって私を苦しめることになります。
次にこのシステムで、PIDや過渡解析を実装するために特定のタスクを終えてスリープしたノードを一定間隔で起動する機能が必要になりました。これは各ノードで各々がsleepを使って定期実行しようとした場合sleepの精度に起因した起動タイミングのズレが起きる可能性があるためです。
ここで、私は各ノードがタスクを完了したら SIGSTOP で自分自身を停止し、システムが SIGCONT で起動する、という形でこの機能を実装しました。
しかし、予想外のことが起きます。
私はLinuxプロではないのでなぜそうなるのかがわからないのですが、通常親プロセスが死んでも子プロセスは影響を受けません。しかし、子プロセスが SIGSTOP で停止している場合は子プロセスも強制終了されます。(おそらく予期しないゾンビプロセスをなくすため?)
ここでshareMemoryを使用してくることが響いてきます。
shareMemoryは通常のメモリーとは違い削除処理を行わなければOS上に残ります。pipeなどは自動的に削除されるのですが、shareMemoryはそうではないため、この仕様と嫌な方に噛み合ってしまったのです。
とはいえ、気づけば話は早いので、 quit コマンドの仕様や SIGINT のハンドルを実装することで解決しました。
でも原因不明のshareMemory残存バグの原因究明には時間がかかりました。
(まさか SIGSTOP でプロセスの終了の仕方が変わるとは思わないじゃん)
そんなこんなで無事wakeupタイマーを実装し、事なきを得ました。