0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【連載】LCIの使い方「エレベーター編」#6|エレベーター呼出階・行き先階指定(CallElevator)

Last updated at Posted at 2025-11-17

こんにちは!この記事では、 株式会社Octa Roboticsが開発・提供している ロボット・設備間連携に特化したマルチベンダー型のインターフェースサービス「LCI」 について、ロボットからの利用方法を連載形式で紹介していきます。

前回の記事では、通信プロトコルの「利用登録(Registration)」を実装しました。

第5回はこちら

今回は、「エレベーター呼出階・行き先階指定(CallElevator)」について実装を行っていきます。

おさらい

エレベーター呼出階・行き先階指定のシーケンスについて再確認していきましょう。

エレベーター呼出階・行き先階指定では、以下の手順でロボットとエレベーター(LCI)間でMQTT通信を行います。

1. ロボットからエレベーターにCallElevatorを送信
2. エレベーターがCallElevatorを受信
3. エレベーターからロボットにCallElevatorResultを送信
4. ロボットがCallElevatorResultを受信

CallElevatorStatusのresultが1の場合は成功CallElevatorStatusのresultが2、3、99の場合は失敗とみなします。

エレベーター呼出階指定

エレベーター呼び出し.png

エレベーター行き先階指定

image.png

次は、トピック(メッセージの宛先)とペイロード(メッセージの中身)を確認していきましょう。

エレベーター呼出階指定エレベーターへの行き先階指定ではCallElevatorとCallElevatorResultをペアとした同じシーケンスを使用します。

プロトコルの仕様

CallElevatorとCallElevatorStatusの仕様を確認していきます。

トピック

 プロトコル トピック
CallElevator /lci/<bldg_id>/<bank_id>/<elevator_id>/CallElevator/<robot_id>
CallElevatorResult /lci/<bldg_id>/<bank_id>/<elevator_id>/CallElevatorResult/<robot_id>

第3回で使用した設定ファイル:server_config_simulator_1-1.yamlを利用する場合には、以下になります。

 プロトコル トピック
CallElevator /lci/simulator/1/1/CallElevator/_uClosc2
CallElevatorResult /lci/simulator/1/1/CallElevatorResult/_uClosc2
  • bldg_id : simulator(設定ファイル内のlci_bldg_id)
  • bank_id : 1(設定ファイル内のlci_bank_id)
  • elevator_id : 1(設定ファイル内のlci_elevator_id)
  • robot_id : _uClosc2(開発用ロボットアカウントのID)

ペイロード

プロトコル ペイロード
CallElevator {
  "origination" : <origination>,
  "origination_door" : <origination_door>,
  "destination" : <destination>,
  "destination_door" : <destination_door>,
  "timestamp" : <timestamp>,
  "robot_id" : <robot_id>
}
CallElevatorResult {
  "result" : <result>,
  "timestamp" : <timestamp>,
  "requested_robot_id" : <requested_robot_id>,
  "requested_timestamp" : <requested_timestamp>
}
  • <origination>:ロボットの出発階
  • <destination>:ロボットの目的階
  • <timestamp>:CallElevatorまたはCallElevatorResultを送信した時間(64bit Unix epoch)
  • <requested_timestamp>:ロボットからCallElevatorされた時間
  • <result> : CallElevatorの結果(1:成功、2:失敗、3:その他エラー、99:管制運転中)
  • <robot_id> : CallElevatorを行うロボットのID(今回は_uClosc2)
  • <requested_robot_id>:CallElevatorしたロボットのrobot_ID(今回は_uClosc2)

origination_door, destination_doorは,origination, destinationのそれぞれの階にフロントドア、リアドアの区別があるエレベーターに使われます。指定方法は以下になります。ペイロードを省略した(記載しない)場合はフロントドアを指定したことになります。

  • フロントドアを指定する場合
    • 出発階の場合 → “origination door”: 1
    • 目的階の場合 → “destination_door”: 1
  • リアドアを指定する場合
    • 出発階の場合 → “origination door”: 2
    • 目的階の場合 → “destination_door”: 2

origination, destinationのそれぞれの階にフロントドア、リアドアの区別がない場合は、origination_door, destination_doorは省略するか、1を設定しましょう。

エレベーター呼出階の指定、行き先階の指定の両方で、行き先階の情報(destination, destination_door)を正しく設定しましょう。

正しく設定しない場合、連携できないエレベーターが存在します。

補足:originationとdestinationの指定方法

originationとdestinationには、設定ファイル内のlci_floor_listに記載されているfloor_id(フロアの名称を表す文字列)を参照します。

今回使用している設定ファイル:server_config_simulator-1-1.yamlでは以下のように記載されています。

server_config_simulator-1-1.yaml
# floor name list from the bottom to the top
lci_floor_list:
- ['B1', True, False]
- 'E'
- 'G'
- 'L'
- '1F'
- '2F'
- '3F'
- ['4F', True, False]
- 'M5'
- 'M6'
- ['8F', True, True]
- ['R', False, True]

記載されているfloor_idの中から、ロボットが呼び出したい階であるorigination、ロボットが移動したい階であるdestinationにそれぞれ指定しましょう。

以下は、ペイロードの一例です。

ロボットが 'B1' から '1F' に移動したい場合
{
  "origination" : 'B1',
  "origination_door" : 1,
  "destination" : '1F',
  "destination_door" : 1,
  "timestamp" : xxxxxx.xxx,
  "robot_id" : _uClosc2
}

originationとdestinationは、必ず設定ファイル内のfloor_idと同じ文字列を指定しましょう。
正しく指定しない場合、その他エラー(result:3)が発生しエレベーター連携ができません。

例:1F(floor_id : '1F')を呼び出し階とした場合にorigination : '1'とする。

サンプルコードの確認

サンプルコードのエレベーター呼出階・行き先階指定に関する箇所を確認しましょう。

サンプルコードでは、ロボットがB1から1Fに移動する際の例が示されています。

do_call_elevator

do_call_elevator関数では、以下を行なっています。

1. ペイロードの作成
2.トピックの作成
3. メッセージのパブリッシュ
4. CallElevatorResultの受信待ち

do_call_elevator
 # Extention. is_rear_door_* == 0: not specified (default), 1: front door, 2: rear door.
def do_call_elevator(self, origination, destination, origination_door: int = 0, destination_door: int = 0, direction=0):
       if not self.is_registered():
           return LciResponse()

       requested_time = int(time.time() * 1000)/1000
       #ペイロードの作成
       payload = json.dumps({
           'origination': str(origination),
           'destination': str(destination),
           'timestamp': requested_time,
           'origination_door': origination_door,
           'destination_door': destination_door,
           'robot_id': self.robot_id})

       #トピックの作成
       api = 'CallElevator'
       topic = f'{self.topic_prefix}/{self.elevator_id}/{api}/{self.robot_id}'

       #メッセージのパブリッシュ
       self._publish(topic, payload)

       #CallElevatorResultの受信待ち
       return self.wait_response(api, requested_time, 3.0)

メイン処理

サンプルコードでは、origination'B1'destination'1F'としています。

main
# Main routine
if __name__ == '__main__':

    #---省略---#

    res = lci_client.do_call_elevator('B1', '1F')
    lci_client.logger.debug(res)
    time.sleep(1)

    res = lci_client.do_request_elevator_status()
    lci_client.logger.debug(res)
    time.sleep(1)

    res = lci_client.do_request_elevator_status()
    lci_client.logger.debug(res)
    time.sleep(1)

    res = lci_client.do_robot_status(RobotStatus.HAS_ENTERED)
    lci_client.logger.debug(res)
    time.sleep(1)

    res = lci_client.do_robot_status(RobotStatus.KEEP_DOOR_OPEN)
    lci_client.logger.debug(res)
    time.sleep(1)

    res = lci_client.do_robot_status(RobotStatus.HAS_GOT_OFF)
    lci_client.logger.debug(res)
    time.sleep(1)

    res = lci_client.do_release()
    lci_client.logger.debug(res)
    time.sleep(1)

サンプルコードの改良

エレベーター呼出階・行き先階指定について、CallElevatorResult内のresultが2, 3, 99の場合のエラー処理を実施する必要性があります。

サンプルコードでは、この処理は未実装です。

これらの処理を行えるようにサンプルコードを改良していきましょう。

resultの意味と発生条件

はじめにCallElevatorResult内のresultの意味と発生条件について確認しましょう。

評価順 <result>の値 意味 発生条件
1 3 その他エラー ペイロードのフィールドの一部又は全部が設定されていない
2 3 その他エラー トピック名のrobot_idと、ペイロードrobot_idが一致しない
3 99 管制運転中 エレベーターが管制運転中のため連携不能
4 3 その他エラー エレベーターがロボット連携モードにも関わらず、ロボットが利用登録されていない。(状態のミスマッチ)
5 2 拒否(失敗) ・エレベーターがロボット連携モードになっていない
・他のロボットが利用中
・エレベーターがロボット連携モードに遷移中
・エレベーターがロボット連携モードを解除中
6 3 その他エラー ・"origination","origination_door","destination","destination_door"のいずれかの値が、設定ファイルに従っていない
・CallElevatorの再送が3回を越えた
・再送したCallElevatorのフィールドが、元のCallElevatorのフィールドと異なっている
・シーケンス上、CallElevatorが許されていない状態で送信している
7 1 成功 エレベーターに呼出階/行き先階を指示できた

各resultの値に応じたロボットの動作

先ほどの表をもとに、resultの値に対してロボットがどのように動作するかを決めていきます。

今回は仕様書をもとに以下のように定めます。

<result> <result>の意味 ロボット側の動作
1 成功 次のステップに進む
2 拒否(失敗) 異常シーケンスに遷移し、連携終了
3 その他エラー 異常シーケンスに遷移し、連携終了
99 管制運転中 異常シーケンスに遷移し、連携終了

LCIからの応答が一定時間経過してもない場合は、利用解除し連携終了とします。

フロー図

resultに応じたロボットの動作をフロー図にしました。
フロー図の流れとしては、

  1. CallElevatorの送信
  2. CallElevatorResultを受信したか確認
    • 受信していない場合は、最大10秒待機
  3. CallElevatorResultにおけるResultの値が1の場合は、利用登録成功と判断し、次のプロトコルに進む
    • resultが2、3、99の場合は利用解除に進む

image.png

CallElevatorResultの最大待機時間は10秒としました。

実装したコード

以下に実装した関数と追記した箇所を示します。

do_call_elevator関数

  • CallElevatorResultのresultに応じた処理を行います。
  • resultが1以外の場合はself.registeredをFalseとします。
  • Falseとすることで、CallElevator以降のシーケンスが実行されないようになります。
do_call_elevator
 def do_call_elevator(self, origination, destination, origination_door: int = 0, destination_door: int = 0, direction=0):
       time_out = 10
        res = self.call_elevator(origination, destination, time_out, origination_door, destination_door, direction)


       if res is not None:               
           if res.result != 1:
               self.registered = False
               logging.error("CallElevator is failed!")


       return res

call_elevator関数

 # Extention. is_rear_door_* == 0: not specified (default), 1: front door, 2: rear door.
def do_call_elevator(self, origination, destination, origination_door: int = 0, destination_door: int = 0, direction=0):
       if not self.is_registered():
           return LciResponse()

       requested_time = int(time.time() * 1000)/1000
       #ペイロードの作成
       payload = json.dumps({
           'origination': str(origination),
           'destination': str(destination),
           'timestamp': requested_time,
           'origination_door': origination_door,
           'destination_door': destination_door,
           'robot_id': self.robot_id})

       #トピックの作成
       api = 'CallElevator'
       topic = f'{self.topic_prefix}/{self.elevator_id}/{api}/{self.robot_id}'

       #メッセージのパブリッシュ
       self._publish(topic, payload)

       #CallElevatorResultの受信待ち
       return self.wait_response(api, requested_time, 3.0)

メイン処理

通信シーケンスをもとに並び替えています。

CallElevatorは、合計2回連携シーケンスの中で実施します。

  • 利用登録(do_registration())後
  • 乗り込み完了(do_robot_status(RobotStatus.HAS_ENTERED)) 後
main
# Main routine
if __name__ == '__main__':


   #—--省略---#

   #dry runによる疎通確認
   res = lci_client.do_registration(True)
   lci_client.logger.debug(res)
   time.sleep(1)
    
   #本番の利用登録
   res = lci_client.do_registration()
   lci_client.logger.debug(res)
   time.sleep(1) 
    
   if res != None:
       lci_client.set_elevator_id(res.elevator_id)

  
   #出発階への呼び出し
   res = lci_client.do_call_elevator('B1', '1F')
   lci_client.logger.debug(res)
   time.sleep(1)


   #エレベーターへの状態取得
   res = lci_client.do_request_elevator_status()
   lci_client.logger.debug(res)
   time.sleep(1)


   #乗り込み
   res = lci_client.do_robot_status(RobotStatus.KEEP_DOOR_OPEN)
   lci_client.logger.debug(res)
   time.sleep(1)


   #乗り込み完了
   res = lci_client.do_robot_status(RobotStatus.HAS_ENTERED)
   lci_client.logger.debug(res)
   time.sleep(1)


   #行き先階への登録
   res = lci_client.do_call_elevator('B1', '1F')
   lci_client.logger.debug(res)
   time.sleep(1)


   #降車
   res = lci_client.do_robot_status(RobotStatus.HAS_GOT_OFF)
   lci_client.logger.debug(res)
   time.sleep(1)


   #利用解除
   res = lci_client.do_release()
   lci_client.logger.debug(res)
   time.sleep(1)

動作確認

作成したコードの動作確認を行います。

今回は、送信したメッセージのtopicにCallElevatorが含まれていた場合、CallElevatorResult内のresultの値を任意に置き換えることで動作確認を実施します。

msg_callback
def msg_callback(self, topic: str, payload_kv: dict):
       parsed_topic = LciClient.ParsedTopic(topic)


       if parsed_topic.api == 'message':
           res = LciMessage()
           res.orig_api = 'message'
           res.type = payload_kv.get('type', 0)
           res.content = payload_kv.get('content', 0)
           res.robot_in_the_car = payload_kv.get('robot_in_the_car', 0)
           res.timestamp = payload_kv.get('timestamp', 0)
           self.message_queue.put(res)
           return


       elif parsed_topic.orig_api == '':
           return
       elif parsed_topic.orig_api == 'RequestElevatorStatus':
           res = ElevatorStatus()
           res.orig_api = 'RequestElevatorStatus'
           res.floor = payload_kv.get('floor', 0)
           res.door = payload_kv.get('door', 0)
           res.direction = payload_kv.get('direction', 0)


       else:
           res = LciResponse()
           res.orig_api = parsed_topic.orig_api


	# ---追記--- #
       #CallElevatorResultのresultを変更する
       if parsed_topic.orig_api == 'CallElevator':
           #res.resultを1, 2, 3, 99のいずれかに設定
           res.result = 1
       else:
           res.result = payload_kv.get('result', 0)

次はresultの各値において出力したログを確認していきます。

resultが1(成功)の場合

エレベーター呼び出し後、次のステップに移っています。

2025-09-16 17:19:48,816    DEBUG Sending PUBLISH (d0, q1, r0, m5), 'b'/lci/simulator/1/1/CallElevator/_uClosc2'', ... (141 bytes)
2025-09-16 17:19:48,844    DEBUG Received PUBACK (Mid: 5)
2025-09-16 17:19:48,981    DEBUG Received PUBLISH (d0, q1, r0, m1), '/lci/simulator/1/1/CallElevatorResult/_uClosc2', ...  (115 bytes)
2025-09-16 17:19:48,981    DEBUG Sending PUBACK (Mid: 1)
2025-09-16 17:19:49,035    DEBUG orig_api: 'CallElevator', result: '1', elevator_id: '1', timestamp: '1758010788.862', requested_robot_id: '_uClosc2', requested_timestamp: '1758010788.816'
2025-09-16 17:19:50,038    DEBUG Sending PUBLISH (d0, q1, r0, m6), 'b'/lci/simulator/1/1/RequestElevatorStatus/_uClosc2'', ... (53 bytes)
・・・

resultが2(拒否)の場合

他のシーケンスを行わず、連携終了しています。

2025-09-18 10:23:03,959    DEBUG Sending PUBLISH (d0, q1, r0, m5), 'b'/lci/simulator/1/1/CallElevator/_uClosc2'', ... (141 bytes)
2025-09-18 10:23:03,987    DEBUG Received PUBACK (Mid: 5)
2025-09-18 10:23:04,173    DEBUG Received PUBLISH (d0, q1, r0, m1), '/lci/simulator/1/1/CallElevatorResult/_uClosc2', ...  (115 bytes)
2025-09-18 10:23:04,173    DEBUG Sending PUBACK (Mid: 1)
ERROR:root:CallElevator is failed!
2025-09-18 10:23:04,175    DEBUG orig_api: 'CallElevator', result: '2', elevator_id: '1', timestamp: '1758158584.113', requested_robot_id: '_uClosc2', requested_timestamp: '1758158583.959'
2025-09-18 10:23:05,179    DEBUG None
2025-09-18 10:23:06,182    DEBUG None
2025-09-18 10:23:07,188    DEBUG None
ERROR:root:CallElevator is failed!
2025-09-18 10:23:08,194    DEBUG None
2025-09-18 10:23:09,197    DEBUG None
2025-09-18 10:23:10,203    DEBUG Sending PUBLISH (d0, q1, r0, m6), 'b'/lci/simulator/1/1/Release/_uClosc2'', ... (53 bytes)
2025-09-18 10:23:10,239    DEBUG Received PUBACK (Mid: 6)
2025-09-18 10:23:10,318    DEBUG Received PUBLISH (d0, q1, r0, m1), '/lci/simulator/1/1/ReleaseResult/_uClosc2', ...  (115 bytes)
2025-09-18 10:23:10,318    DEBUG Sending PUBACK (Mid: 1)
2025-09-18 10:23:10,364    DEBUG orig_api: 'Release', result: '1', elevator_id: '1', timestamp: '1758158590.312', requested_robot_id: '_uClosc2', requested_timestamp: '1758158590.203'

resultが3 (その他エラー)の場合

他のシーケンスを行わず、連携終了しています。

2025-09-18 10:24:43,110    DEBUG Sending PUBLISH (d0, q1, r0, m5), 'b'/lci/simulator/1/1/CallElevator/_uClosc2'', ... (141 bytes)
2025-09-18 10:24:43,145    DEBUG Received PUBACK (Mid: 5)
2025-09-18 10:24:43,312    DEBUG Received PUBLISH (d0, q1, r0, m1), '/lci/simulator/1/1/CallElevatorResult/_uClosc2', ...  (115 bytes)
2025-09-18 10:24:43,312    DEBUG Sending PUBACK (Mid: 1)
ERROR:root:CallElevator is failed!
2025-09-18 10:24:43,329    DEBUG orig_api: 'CallElevator', result: '3', elevator_id: '1', timestamp: '1758158683.191', requested_robot_id: '_uClosc2', requested_timestamp: '1758158683.109'
2025-09-18 10:24:44,334    DEBUG None
2025-09-18 10:24:45,338    DEBUG None
2025-09-18 10:24:46,342    DEBUG None
ERROR:root:CallElevator is failed!
2025-09-18 10:24:47,348    DEBUG None
2025-09-18 10:24:48,351    DEBUG None
2025-09-18 10:24:49,352    DEBUG Sending PUBLISH (d0, q1, r0, m6), 'b'/lci/simulator/1/1/Release/_uClosc2'', ... (53 bytes)
2025-09-18 10:24:49,385    DEBUG Received PUBACK (Mid: 6)
2025-09-18 10:24:49,490    DEBUG Received PUBLISH (d0, q1, r0, m1), '/lci/simulator/1/1/ReleaseResult/_uClosc2', ...  (115 bytes)
2025-09-18 10:24:49,491    DEBUG Sending PUBACK (Mid: 1)
2025-09-18 10:24:49,514    DEBUG orig_api: 'Release', result: '1', elevator_id: '1', timestamp: '1758158689.461', requested_robot_id: '_uClosc2', requested_timestamp: '1758158689.352'

resultが99(管制運転エラー)の場合

他のシーケンスを行わず、連携終了しています。

2025-09-18 10:25:40,854    DEBUG Sending PUBLISH (d0, q1, r0, m5), 'b'/lci/simulator/1/1/CallElevator/_uClosc2'', ... (141 bytes)
2025-09-18 10:25:41,061    DEBUG Received PUBACK (Mid: 5)
2025-09-18 10:25:41,102    DEBUG Received PUBLISH (d0, q1, r0, m1), '/lci/simulator/1/1/CallElevatorResult/_uClosc2', ...  (115 bytes)
2025-09-18 10:25:41,102    DEBUG Sending PUBACK (Mid: 1)
ERROR:root:CallElevator is failed!
2025-09-18 10:25:41,119    DEBUG orig_api: 'CallElevator', result: '99', elevator_id: '1', timestamp: '1758158740.968', requested_robot_id: '_uClosc2', requested_timestamp: '1758158740.853'
2025-09-18 10:25:42,125    DEBUG None
2025-09-18 10:25:43,125    DEBUG None
2025-09-18 10:25:44,126    DEBUG None
ERROR:root:CallElevator is failed!
2025-09-18 10:25:45,128    DEBUG None
2025-09-18 10:25:46,128    DEBUG None
2025-09-18 10:25:47,134    DEBUG Sending PUBLISH (d0, q1, r0, m6), 'b'/lci/simulator/1/1/Release/_uClosc2'', ... (53 bytes)
2025-09-18 10:25:47,170    DEBUG Received PUBACK (Mid: 6)
2025-09-18 10:25:47,309    DEBUG Received PUBLISH (d0, q1, r0, m1), '/lci/simulator/1/1/ReleaseResult/_uClosc2', ...  (115 bytes)
2025-09-18 10:25:47,309    DEBUG Sending PUBACK (Mid: 1)
2025-09-18 10:25:47,343    DEBUG orig_api: 'Release', result: '1', elevator_id: '1', timestamp: '1758158747.214', requested_robot_id: '_uClosc2', requested_timestamp: '1758158747.134'

連載記事リスト

他の記事もチェックしてみてください!


次回の記事では、通信プロトコルの「エレベーターの状態取得」についてサンプルコードを改良しながら詳しく解説する予定です!
ご興味があれば「いいね」やフォローをいただけると励みになります!


0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?