1.はじめに
以前の記事(ROS2・PlanSys2 チュートリアルチャレンジ(2) - hotch-potch, Note to self)の応用として、ロボットに変化する条件を与えて、プランの結果が変わることを確認してみます。
Terminal Usage — ROS2 Planning System 2 1.0.0 documentation
2.概要
(1)ロボットの動きの復習
チュートリアルplansys2_simple_example
では、
移動ロボットのバッテリーの残量は以下2つの状態を持ちます。
- battery_low
- battery_full
チュートリアルでは、以下の順番でプランが進みます。
- 初期状態のKnowlegeでは、
predicate (battery_low leia)
となっています - プラン実行と同時に、
askcharge
アクションが実行されます - battery_lowなので、充電エリア
chargingroom
にワープ(?)し、charge
アクションが実行されます - 充電が終わると、
predicate (battery_low leia)
を消して、predicate (battery_full leia)
に書き換えます - 地点間の移動
move
アクションをはじめます
(2)改修後のロボットの動きの概要
バッテリーの残量を数値で管理し、移動ごとにバッテリーの消耗させて、一定以下に消耗したら再度充電するようにします。
下記にバッテリー残量の値を保持します。
battery_full
初期状態のKnowlegeでは、
predicate (battery_level leia)
とします。この時点では、battery_level = 0
です。- プラン実行と同時に、
askcharge
アクションが実行されます battery_level = 0
なので、充電エリアchargingroom
にワープ(?)し、charge
アクションが実行されます- 充電が終わると、
battery_level= battery_level + 5
に書き換えます - 地点間の移動
move
アクションをはじめます。移動のたびに電力を消費し、battery_level= battery_level - 1
にします。 - 移動中に
battery_level < 2
になったら、askcharge
アクションが実行され充電します - 引き続き、地点間を移動します
これを、をPDDLで管理するよう改造してみました。
3.PDDLの読み解きと改修
(1)オリジナルのPDDLの読み解き
src/plansys2_simple_example/pddl/simple_example.pddl
こちらは、クローンしてきたソースコードの中で、simple_example
を実行するときに使うPDDLファイルです。
PDDLの読み解きは、こちらの資料を参考にしました。
(以下、長くなりますので、ご勘弁を…)
(define (domain simple) (:requirements :strips :typing :adl :fluents :durative-actions) ;; Types ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (:types robot room ) ;; end Types ;;;;;;;;;;;;;;;;;;;;;;;;; ;; Predicates ;;;;;;;;;;;;;;;;;;;;;;;;; (:predicates (robot_at ?r - robot ?ro - room) (connected ?ro1 ?ro2 - room) (battery_full ?r - robot) (battery_low ?r - robot) (charging_point_at ?ro - room) ) ;; end Predicates ;;;;;;;;;;;;;;;;;;;; ;; Functions ;;;;;;;;;;;;;;;;;;;;;;;;; (:functions ) ;; end Functions ;;;;;;;;;;;;;;;;;;;; ;; Actions ;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;durative-action ;持続アクション ;(完了までに時間がかかるアクション) ;移動 (:durative-action move ;このアクションに与えるパラメーターの定義 :parameters (?r - robot ?r1 ?r2 - room) ;アクションが完了するまでの所要時間、計画時に使う :duration ( = ?duration 5) ;このアクションをアクティブ状態に維持する条件 :condition (and ;最初からtrueの必要な条件 ;出発元と到着先が繋がっていること (at start(connected ?r1 ?r2)) ;ロボットが出発元にいること (at start(robot_at ?r ?r1)) ;最初と最後を含む、アクション全体を通じてtrueが必要な条件 (over all(battery_full ?r)) ) ;このアクションがアクティブになったことにより変化させる対象 :effect (and ;アクティブ状態になった直後に変化させる対象 ;ロボットが以前にいた場所を削除 (at start(not(robot_at ?r ?r1))) ;アクティブ状態が終わる直前に変化させる対象 ;ロボットが到着した先をセット (at end(robot_at ?r ?r2)) ) ) ;充電残量の問い合わせ (:durative-action askcharge ;このアクションに与えるパラメーターの定義 :parameters (?r - robot ?r1 ?r2 - room) :duration ( = ?duration 5) ;このアクションをアクティブ状態に維持する条件 :condition (and ;最初からtrueの必要な条件 ;ロボットが出発元にいること (at start(robot_at ?r ?r1)) ;充電場所が設定されていること (at start(charging_point_at ?r2)) ) ;このアクションがアクティブになったことにより変化させる対象 :effect (and ;アクティブ状態になった直後に変化させる対象 ;ロボットが以前にいた場所を削除 (at start(not(robot_at ?r ?r1))) ;アクティブ状態が終わる直前に変化させる対象 ;ロボットが到着した先をセット (at end(robot_at ?r ?r2)) ) ) (:durative-action charge ;このアクションに与えるパラメーターの定義 :parameters (?r - robot ?ro - room) ;アクションが完了するまでの所要時間、計画時に使う :duration ( = ?duration 5) ;このアクションをアクティブ状態に維持する条件 :condition (and ;最初からtrueの必要な条件 ;ロボットが出発元にいること (at start(robot_at ?r ?ro)) ;充電場所が設定されていること (at start(charging_point_at ?ro)) ) ;このアクションがアクティブになったことにより変化させる対象 :effect (and ;アクティブ状態が終わる直前に変化させる対象 (at end(not(battery_low ?r))) (at end(battery_full ?r)) ) ) ;; end Actions ;;;;;;;;;;;;;;;;;;;;;;;;;;;; ) ;; end Domain ;;;;;;;;;;;;;;;;;;;;;;;;
(2)PDDLの改修
改修に入る前に、オリジナルをバックアップしておきましょう。
$ cd ~/gitwork/p2tuto/ $ cp -p src/plansys2_simple_example/pddl/simple_example.pddl src/plansys2_simple_example/pddl/simple_example.org.pddl
前章の「改修後のロボットの動きの概要」に合わせて、PDDLを書き換えます。
(define (domain simple) (:requirements :strips :typing :adl :fluents :durative-actions) ;; Types ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (:types robot room ) ;; end Types ;;;;;;;;;;;;;;;;;;;;;;;;; ;; Predicates ;;;;;;;;;;;;;;;;;;;;;;;;; (:predicates (robot_at ?r - robot ?ro - room) (connected ?ro1 ?ro2 - room) (battery_full ?r - robot) (battery_low ?r - robot) (charging_point_at ?ro - room) ) ;; end Predicates ;;;;;;;;;;;;;;;;;;;; ;; Functions ;;;;;;;;;;;;;;;;;;;;;;;;; (:functions ;(★追加)バッテリー残量 (battery_level ?r - robot) ) ;; end Functions ;;;;;;;;;;;;;;;;;;;; ;; Actions ;;;;;;;;;;;;;;;;;;;;;;;;;;;; (:durative-action move :parameters (?r - robot ?r1 ?r2 - room) :duration ( = ?duration 5) :condition (and (at start(connected ?r1 ?r2)) (at start(robot_at ?r ?r1)) ;(★追加)バッテリー残量確認、2以上でmoveできる (at start(>= (battery_level ?r) 2)) ;(★コメントアウト)最初と最後を含む、アクション全体を通じてtrueの必要な条件 ;(over all(battery_full ?r)) ) :effect (and (at start(not(robot_at ?r ?r1))) (at end(robot_at ?r ?r2)) ;(★追加)バッテリーを-1消費させる (at end(decrease (battery_level ?r) 1)) ) ) (:durative-action askcharge :parameters (?r - robot ?r1 ?r2 - room) :duration ( = ?duration 5) :condition (and (at start(robot_at ?r ?r1)) (at start(charging_point_at ?r2)) ) :effect (and (at start(not(robot_at ?r ?r1))) (at end(robot_at ?r ?r2)) ) ) (:durative-action charge :parameters (?r - robot ?ro - room) :duration ( = ?duration 5) :condition (and (at start(robot_at ?r ?ro)) (at start(charging_point_at ?ro)) ) :effect (and ;(★コメントアウト)アクティブ状態が終わる直前に変化させる対象 ;(at end(not(battery_low ?r))) ;(at end(battery_full ?r)) ;(★追加)バッテリーを+5充電する (at end(increase (battery_level ?r) 5)) ) ) ;; end Actions ;;;;;;;;;;;;;;;;;;;;;;;;;;;; ) ;; end Domain ;;;;;;;;;;;;;;;;;;;;;;;;
計算に使う場合はfunctionsに書くこと
先ほどのbattery_level
を、試しに、:functions
ではなく:predicates
に書いてみると・・・
… ;; Predicates ;;;;;;;;;;;;;;;;;;;;;;;;; (:predicates (robot_at ?r - robot ?ro - room) (connected ?ro1 ?ro2 - room) (battery_full ?r - robot) (battery_low ?r - robot) (charging_point_at ?ro - room) ;(★追加)バッテリー残量 (battery_level ?r - robot) ) ;; end Predicates ;;;;;;;;;;;;;;;;;;;; ;; Functions ;;;;;;;;;;;;;;;;;;;;;;;;; (:functions ;(★追加)バッテリー残量 ;(battery_level ?r - robot) ) ;; end Functions ;;;;;;;;;;;;;;;;;;;; …
VSCodeがエラーを出してきました。
VSCoceのこちらの拡張機能がおすすめです。
(3) Knowledge(commandsファイル)の改修
続いて、Knowledgeが書かれたcommandsファイルを改修します。
改修に入る前に、オリジナルをバックアップしておきましょう。
$ cd ~/gitwork/p2tuto/ $ cp -p src/plansys2_simple_example/launch/commands src/plansys2_simple_example/launch/commands.org
commandsファイルにバッテリー残量の記述を追加します。
set instance leia robot set instance entrance room set instance kitchen room set instance bedroom room set instance dinning room set instance bathroom room set instance chargingroom room set predicate (connected entrance dinning) set predicate (connected dinning entrance) set predicate (connected dinning kitchen) set predicate (connected kitchen dinning) set predicate (connected dinning bedroom) set predicate (connected bedroom dinning) set predicate (connected bathroom bedroom) set predicate (connected bedroom bathroom) set predicate (connected chargingroom kitchen) set predicate (connected kitchen chargingroom) set predicate (charging_point_at chargingroom) set predicate (battery_low leia) set predicate (robot_at leia entrance) set function (battery_level leia) ; ←ここを追加 set goal (and(robot_at leia showerroom))
■注意:上記ソースの「 ; ←ここを追加」は、実際にはファイルに書かないでください
4.プランの実行と確認
以前のチュートリアルplansys2_simple_example
の手順に倣って、プランの作成と実行を試行します。
ターミナル1
simple_example_launch
を、以前作成したシェルスクリプト経由で起動します。
$ cd ~/gitwork/p2tuto
$ ./tsimple_example_launch.sh
ターミナル2
plansys2_terminal
を、以前作成したシェルスクリプト経由で起動します。
$ cd ~/gitwork/p2tuto $ ./tpterm.sh [INFO] [1715338910.245917057] [terminal]: No problem file specified. ROS2 Planning System console. Type "quit" to finish >
ターミナル3
rqt
を、以前作成したシェルスクリプト経由で起動します。
$ cd ~/gitwork/p2tuto
$ ./trqt.sh
プラン1回目
plansys2_terminalから、以下を入力します。
> source src/plansys2_simple_example/launch/commands done
この時、rqtは下記の内容になります。
今回からKnowledgeに、function (battery_level leia) = 0
が表示されます。
> get plan plan: 0: (askcharge leia entrance chargingroom) [5] 5.001: (charge leia chargingroom) [5] 10.002: (move leia chargingroom kitchen) [5] 15.003: (move leia kitchen dinning) [5] 20.004: (move leia dinning bedroom) [5] 25.005: (move leia bedroom bathroom) [5]
これを図示するとこうなります。(チュートリアルと同じです)
graph LR A === B B === C C === D B === E E === F A --1(askcharge/bat=0)--> D D --(charge/bat=5)--> D D --2(move/bat=4)--> C C --3(move/bat=3)--> B B --4(move/bat=2)--> E E --5(move/bat=1)--> F A(entrance</br>(初めにロボットがいるところ)) B(dining) C(kitchen) D(chargingroom</br>(充電場所)) E(bedroom) F(bathroom</br>(ロボットに向かわせる先))
プランを実行します。
> run [INFO] [1715339372.462591623] [executor_client]: Plan Succeeded Successful finished
2つ目のプラン、(charge leia chargingroom)
が終わった時点で、
バッテリー残量function (battery_level leia) = 5
に充電されました。
最後のプランを終了した時点で、
バッテリー残量function (battery_level leia) = 1
となります。
bathroom
に到着後
プラン2回目
現在ロボットはbathroom
にいるので、
今度はentrance
に向かわせます。
> set goal (and(robot_at leia entrance)) > get plan plan: 0: (askcharge leia bathroom chargingroom) [5] 5.001: (charge leia chargingroom) [5] 10.002: (move leia chargingroom kitchen) [5] 15.003: (move leia kitchen dinning) [5] 20.004: (move leia dinning entrance) [5]
2回目はこのようなプランになりました。 これを図示するとこうなります。
graph LR A === B B === C C === D B === E E === F F --1(askcharge/bat=1)--> D D --(charge/bat=6)--> D D --2(move/bat=5)--> C C --3(move/bat=4)--> B B --4(move/bat=3)--> A A(entrance</br>(ロボットに向かわせる先)) B(dining) C(kitchen) D(chargingroom</br>(充電場所)) E(bedroom) F(bathroom</br>(初めにロボットがいるところ))
ここで使っているPDDLでは、 moveアクションの開始条件の指定が下記のようになっています。
(:durative-action movemove … :condition (and … ;(★追加)バッテリー残量確認、2以上でmoveできる (at start(>= (battery_level ?r) 2)) …
そのため、現時点のバッテリー残量function (battery_level leia) = 1
なので、
2回目のプランでも最初に充電に向かうプランが組まれています。
> run [INFO] [1715339600.416725752] [executor_client]: Plan Succeeded Successful finished >
entrance
に到着後
最後のmoveを終了した時点で、
バッテリー残量function (battery_level leia) = 3
となります。
プラン3回目
現在ロボットはentrance
にいるので、
今度は再びbathroom
に向かわせます。
> set goal (and(robot_at leia bathroom)) > get plan plan: 0: (move leia entrance dinning) [5] 5.001: (move leia dinning bedroom) [5] 10.002: (askcharge leia bedroom chargingroom) [5] 15.003: (charge leia chargingroom) [5] 20.004: (move leia chargingroom kitchen) [5] 25.005: (move leia kitchen dinning) [5] 30.006: (move leia dinning bedroom) [5] 35.007: (move leia bedroom bathroom) [5] > run
3回目はやや複雑なプランになりました。 これを図示するとこうなります。
graph LR A === B B === C C === D B === E E === F A --1(move/bat=2)--> B B --2(move/bat=1)--> C C --3(askcharge/bat=1)--> D D --(charge/bat=6)--> D D --4(move/bat=5)--> C C --5(move/bat=4)--> B B --6(move/bat=3)--> E E --7(move/bat=2)--> F A(entrance</br>(初めにロボットがいるところ)) B(dining) C(kitchen) D(chargingroom</br>(充電場所)) E(bedroom) F(bathroom</br>(ロボットに向かわせる先))
entrance
からdinning
にたどり着いた時点で、function (battery_level leia) = 3 → 2
になります- moveし始める前の、バッテリー残量の条件
(at start(>= (battery_level ?r) 2))
を下回るので、ロボットは充電に行きます - 充電を終えると、バッテリー残量
function (battery_level leia) = 3
となります - 引き続き、
bathroom
に向かいます - 最後のmoveを終了した時点で、バッテリー残量
function (battery_level leia) = 2
となります。
満充電になった直後
bathroom
に到着後
プラン4回目
現在ロボットはbathroom
にいるので、
最後にdinning
に向かわせます。
> set goal (and(robot_at leia dinning)) > get plan plan: 0: (move leia bathroom bedroom) [5] 5.001: (askcharge leia bedroom chargingroom) [5] 10.002: (charge leia chargingroom) [5] 15.003: (move leia chargingroom kitchen) [5] 20.004: (move leia kitchen dinning) [5] > run
これを図示するとこうなります。
graph LR A === B B === C C === D B === E E === F F --1(charge/bat=1)--> E E --2(askcharge/bat=1)--> D D --(charge/bat=6)--> D D --3(move/bat=5)--> C C --4(move/bat=4)--> B A(entrance) B(dining</br>(ロボットに向かわせる先)) C(kitchen) D(chargingroom</br>(充電場所)) E(bedroom) F(bathroom</br>(初めにロボットがいるところ))
最後はこのような状態で終わりました。
dinning
に到着後
5.まとめ
ロボットに変化する条件を与えて、プランの結果が変わることを確認しました。