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)

0
Last updated at Posted at 2025-11-17

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

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

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

前回 : #5 エレベーター利用登録(Registration) / 次回 : #7 エレベーターの状態取得(RequestElevatorStatus)

おさらい

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

エレベーター呼出階・行き先階指定では、以下の手順でロボットとエレベーター(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の仕様を確認していきます。

トピック

 API名 トピック
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を利用する場合には、以下になります。

 API名 トピック
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)

ペイロード

API名 ペイロード
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関数

do_call_elevatorからcall_elevatorに変更しました.

class LciClient
 # 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):
+ def 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回一度のエレベーター連携で実施します。

  • 1回目:利用登録(do_registration())後
  • 2回目:乗り込み完了(do_robot_status(RobotStatus.HAS_ENTERED)) 後
main routine
# 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)

動作確認

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

今回は、msg_callback関数に以下のように追記を行い、CallElevatorResult内のresultの値を任意に置き換えることで動作確認を実施します。

class LciClient
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(成功)の場合

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

  • 17:19:48,816 : CallElevatorの送信
  • 17:19:48,981 : CallElevatorResultの受信
  • 17:19:49,035 : CallElevatorResultのペイロード
  • 17:19:50,038 : RequestElevatorStatusの送信
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,981    DEBUG Received PUBLISH (d0, q1, r0, m1), '/lci/simulator/1/1/CallElevatorResult/_uClosc2', ...  (115 bytes)
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(拒否)の場合

CallElevator以外のステップを行わず、利用解除(Release)に進んでいます。

  • 10:23:03,959 : CallElevatorの送信
  • 10:23:04,173 : CallElevatorResultの受信
  • 10:23:04,175 : CallElevatorResultのペイロード
  • 10:23:05,179 ~ 10:23:09,197 : CallElevator以降のステップを実施(Noneは未実施)
  • 10:23:10,203 : Releaseの送信
  • 10:23:10,318 : ReleaseResultの受信
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:04,173    DEBUG Received PUBLISH (d0, q1, r0, m1), '/lci/simulator/1/1/CallElevatorResult/_uClosc2', ...  (115 bytes)
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,318    DEBUG Received PUBLISH (d0, q1, r0, m1), '/lci/simulator/1/1/ReleaseResult/_uClosc2', ...  (115 bytes)

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

CallElevator以外のステップを行わず、利用解除(Release)に進んでいます。

  • 10:24:43,110 : CallElevatorの送信
  • 10:24:43,312 : CallElevatorResultの受信
  • 10:24:43,329 : CallElevatorResultのペイロード
  • 10:24:44,334 ~ 10:24:48,351 : CallElevator以降のステップを実施(Noneは未実施)
  • 10:24:49,352 : Releaseの送信
  • 10:24:49,490 : ReleaseResultの受信
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,312    DEBUG Received PUBLISH (d0, q1, r0, m1), '/lci/simulator/1/1/CallElevatorResult/_uClosc2', ...  (115 bytes)
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,490    DEBUG Received PUBLISH (d0, q1, r0, m1), '/lci/simulator/1/1/ReleaseResult/_uClosc2', ...  (115 bytes)

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

CallElevator以外のステップを行わず、利用解除(Release)に進んでいます。

  • 10:25:40,854 : CallElevatorの送信
  • 10:25:41,102 : CallElevatorResultの受信
  • 10:25:41,119 : CallElevatorResultのペイロード
  • 10:25:42,125 ~ 10:25:46,128 : CallElevator以降のステップを実施(Noneは未実施)
  • 10:25:47,134 : Releaseの送信
  • 10:25:47,309 : ReleaseResultの受信
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,102    DEBUG Received PUBLISH (d0, q1, r0, m1), '/lci/simulator/1/1/CallElevatorResult/_uClosc2', ...  (115 bytes)
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,309    DEBUG Received PUBLISH (d0, q1, r0, m1), '/lci/simulator/1/1/ReleaseResult/_uClosc2', ...  (115 bytes)

その他

サンプルコードは、以下の二つがアップロードされています。

  • lci_client.py : オリジナル
  • lci_client_tech_blog.py : 本ブログを通して改良したもの

連載記事リスト

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


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


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?