キノコの自省録

日々適当クリエイト

シアーハートアタックの状態遷移設計

ラズパイで作ったシアーハートアタックの状態遷移がどうなっていて、どう実現しているのかというソフトウェアサイドのお話です。

ソフトウェアの話はいいかーと思っていたのですが、そりゃ自分がソフト屋だからかなあと思い直して文章を書いた次第。

シアーハートアタックについてはこちらをどうぞ。

kinokorori.hatenablog.com

状態遷移概要

図にしました。その方が説明早いので。

f:id:kinokorori:20170320203916p:plain

図の通り、全部で4状態です。

停止状態

ジッと黙っている状態です。

かといって何もしていないわけではなく、室温とターゲット温度の差分を取って、熱源がないかチェックしています。

 

発見状態

熱源を検知している状態です。モーター回して前進します。

前進中も、熱源チェックは継続します。

なお、停止状態から遷移した場合は、LEDランプを光らせて、「コッチヲ見ロッ!」と喋ります。

 

ロスト直後

前進中に熱源をロストした状態です。1秒程度静止します。

その間に熱源を再検知した場合は、そのまま発見状態へ戻ります。

完全にロストした場合は、探索状態へ遷移します。

 

探索

超信地旋回で、熱源を探します。

発見したら発見状態へ、旋回しても見つからなければ、停止状態に移行します。

ソフト設計

上記の通り、そんな複雑ではないです。が、それでも適当にif文で書くと、めちゃくちゃ苦労します。

状態管理と言えばStateパターン。ということで、Stateパターンを使って実装しました。

Stateパターンの親クラスは、こんな感じで実装してました。

STATE_IDLE = 0
STATE_LOST = 1
STATE_LOSTTURN = 2
STATE_FIND = 3
STATE_STAY = 255
    
class SuperState:
    #static
    thermo_ = None

    # 初期化メソッド
    # @param mystate_id 自分のステート番号
    def __init__(self, mystate_id):
        self.mystate_ = mystate_id

    # 自Stateに切り替わった時に呼び出されます
    # @param prev_state 直前のState
    def enterState(self, prev_state):
        pass
        
    # 状態をチェックして、次に遷移すべきStateを決定します
    # @return 次State (STATE_STAYの場合は据え置き)
    def check(self):
        return STATE_STAY
    
    # 自分のステートIDを返します。
    def mystate(self):
        return self.mystate_
    

このステートを保持している側の動作は、以下のようになっています。

def main():
    state_ = state.StateIdle()
    state_.enterState(state.STATE_IDLE)
    
    while(True):
        current_state = state_.mystate()
        next_state = state_.check()
        if next_state == state.STATE_STAY:
            time.sleep(0.5)
            continue
        elif next_state == state.STATE_FIND:
            state_ = state.StateFind()
        elif next_state == state.STATE_LOST:
            state_ = state.StateLost()
        elif next_state == state.STATE_LOSTTURN:
            state_ = state.StateLostTurn()
        elif next_state == state.STATE_IDLE:
            state_ = state.StateIdle()
            
        state_.enterState(current_state)
            
        time.sleep(0.10)

やっていることは非常に単純です。

現在のステートクラスに対して、次に何のステートにするかをチェックさせます(→checkメソッド)。

もし、STATE_STAYならばそのまま継続。別のステートなら、その対応ステートクラスをインスタンス化して、enterStateを呼び出します。

この動作を延々と繰り返しているだけです。

各ステートクラスに次状態の脳みそを持たせているため、上記main()関数内は、機械的にステートIDに対応するインスタンスを生成するだけになっています。

この仕組みに従って、具象Stateクラスを実装すればいいだけです。

例えば、発見状態(STATE_FIND)なら、こんな疑似コードになります。

def enterState(self, prev_state):
    # if prev_stateがSTATE_IDLEなら: LED点灯、コッチヲ見ロッと発声
    # モーターを前進モードにして回転

def check(self):
    # if 室温と赤外線温度が所定値未満: return STATE_LOST
    # そうでなければそのまま継続→ return STATE_STAY 

シンプル。2つのメソッドを実装すればいいだけです。

後はモーター制御だったり、LED点灯だったり、温度センサーだったりのアクセス手段を用意すれば終わり。

 

センサー系制御やると、状態管理が結構必要とされることが多いので、Stateパターンを使うと大変楽になれると思います。