こんにちは!この記事では、 株式会社Octa Roboticsが開発・提供している ロボット・設備間連携に特化したマルチベンダー型のインターフェースサービス「LCI」 について、ロボットからの利用方法を連載形式で紹介していきます。
前回の記事では、通信プロトコルの「利用登録(Registration)」を実装しました。
今回は、「エレベーター呼出階・行き先階指定(CallElevator)」について実装を行っていきます。
おさらい
エレベーター呼出階・行き先階指定のシーケンスについて再確認していきましょう。
エレベーター呼出階・行き先階指定では、以下の手順でロボットとエレベーター(LCI)間でMQTT通信を行います。
1. ロボットからエレベーターにCallElevatorを送信
2. エレベーターがCallElevatorを受信
3. エレベーターからロボットにCallElevatorResultを送信
4. ロボットがCallElevatorResultを受信
CallElevatorStatusのresultが1の場合は成功、CallElevatorStatusのresultが2、3、99の場合は失敗とみなします。
エレベーター呼出階指定
エレベーター行き先階指定
次は、トピック(メッセージの宛先)とペイロード(メッセージの中身)を確認していきましょう。
エレベーター呼出階指定、エレベーターへの行き先階指定では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では以下のように記載されています。
# 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にそれぞれ指定しましょう。
以下は、ペイロードの一例です。
{
"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の受信待ち
# 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 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に応じたロボットの動作をフロー図にしました。
フロー図の流れとしては、
- CallElevatorの送信
- CallElevatorResultを受信したか確認
- 受信していない場合は、最大10秒待機
- CallElevatorResultにおけるResultの値が1の場合は、利用登録成功と判断し、次のプロトコルに進む
- resultが2、3、99の場合は利用解除に進む
CallElevatorResultの最大待機時間は10秒としました。
実装したコード
以下に実装した関数と追記した箇所を示します。
do_call_elevator関数
- CallElevatorResultのresultに応じた処理を行います。
- resultが1以外の場合はself.registeredをFalseとします。
- Falseとすることで、CallElevator以降のシーケンスが実行されないようになります。
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 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の値を任意に置き換えることで動作確認を実施します。
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'
連載記事リスト
- (1)はじめに・自己紹介・システム概要
- (2)開発用アカウント発行・サンプルコード
- (3)MQTTのブローカー接続・MQTTについて
- (4)エレベーター連携の全体シーケンス
- (5)エレベーター利用登録(Registration)
- (6)エレベーター呼出階・行き先階指定(CallElevator)
他の記事もチェックしてみてください!
次回の記事では、通信プロトコルの「エレベーターの状態取得」についてサンプルコードを改良しながら詳しく解説する予定です!
ご興味があれば「いいね」やフォローをいただけると励みになります!


