こんにちは!この記事では、株式会社Octa Roboticsが開発・提供しているロボット・設備間連携に特化したマルチベンダー型のインターフェースサービス「LCI」について、ロボットからの利用方法を連載形式で紹介していきます。
前回の記事では、「LCI」を利用して仮想のエレベータと連携する全体シーケンスを紹介しました。
今回は、全体シーケンスの中でも「利用登録(Registration)」について、実装を行っていきます。
おさらい
利用登録のシーケンスについて再確認していきましょう。
利用登録では、以下の手順でロボットとエレベーター(LCI)間でMQTT通信を行います。
1. ロボットからエレベーターにRegistrationを送信
2. エレベーターがRegistrationを受信
3. エレベーターからロボットにRegistrationResultを送信
4. ロボットがRegistrationResultを受信
RegistrationResultのresultが1の場合は成功、RegistrationResultのresultが2、3、99のいずれかの場合は失敗とみなします。
MQTT通信はPublish(送信)/Subscribe(受信)パターンのメッセージングプロトコルです。
RegistrationResultが返るまでの応答時間は、デフォルトで最大180秒です。ただし、エレベーターごとに異なる可能性があるため、LCIの設定ファイル(server_config.yaml等)の、 lci_timeout_secに基づいて設定してください
エレベーターに対してロボット1台の利用になります。
そのため、他のロボットが利用している場合にはエレベーターを呼び出すまでの時間が長くなってしまう場合があります。
プロトコルの仕様
RegistrationとRegistrationResultの仕様を確認します。
今回は、MQTT通信におけるメッセージの宛先であるトピック、メッセージの中身であるペイロードをそれぞれ確認していきましょう。
トピック(メッセージの宛先)
プロトコル仕様書によると、Registration、RegistrationResult時のトピックはそれぞれ以下になります。
| プロトコル | Topic |
|---|---|
| Registration | /lci/<bldg_id>/<bank_id>/<elevator_id>/<Registration>/<robot_id> |
| RegistrationResult | /lci/<bldg_id>/<bank_id>/<elevator_id>/RegistrationResult/<robot_id> |
第3回で使用した設定ファイル:server_config_simulator_1-1.yamlを利用する場合には、以下になります。
| プロトコル | Topic |
|---|---|
| Registration | /lci/simulator/1/1/Registration/_uClosc2 |
| RegistrationResult | /lci/simulator/1/1/RegistrationResult/_uClosc2 |
- bldg_id : simulator(設定ファイル内のlci_bldg_id)
- bank_id : 1(設定ファイル内のlci_bank_id)
- elevator_id : 1(設定ファイル内のlci_elevator_id)
- robot_id : _uClosc2(開発用ロボットアカウントのID)
ペイロード(メッセージの中身)
MQTT通信におけるペイロードとは、メッセージの中身のことを指します。
このペイロードには、テキストデータ、バイナリデータ、XML、または JSONファイルなどのデータ形式が使われるようです。
LCIにおいては、JSON形式が用いられており、利用登録におけるペイロードはRegistration、RegistrationResultそれぞれで以下になります。
- Registration (ロボットからエレベーターに送信)
| 条件 | ペイロード |
|---|---|
| 通常時 | { "robot_id" : <robot_id>, "timestamp" : <timestamp> } |
| Dry Run時 | { "robot_id" : <robot_id>, "timestamp" : <timestamp>, "dry_run" : true } |
- <robot_id> : Registrationを行うロボットのID(今回は_uClosc2)
timestampは、整数部を64bit Unix epoch、小数部をミリ秒として表した時間を入力します。
Unix epochは、協定世界時(UTC)1970年1月1日午前0時からの経過秒数で日時を表したものです。
Dry Runについては、trueに設定すると実際には利用登録を行いません。 また、ロボット連携モードの要求・遷移も行いません。
そのため、通信状況の確認などに役立てることができます。
- RegistrationResult(エレベーターからロボットに送信)
| 条件 | ペイロード |
|---|---|
| 通常時 | { "result" : <result>, "elevator_id" : <elevator_id>, "timestamp" : <timestamp>, "requested_robot_id" : <requested_robot_id>, "requested_timestamp" : <requested_timestamp> } |
| Dry Run時 | { "result" : <result>, "elevator_id" : <elevator_id>, "timestamp" : <timestamp>, "requested_robot_id" : <requested_robot_id>, "requested_timestamp" : <requested_timestamp>, "dry_run" : true } |
- <result> : Registrationの結果(1:成功、2:失敗、3:その他エラー、99:管制運転中)
- <timestamp> : RegistrationまたはRegistratonResultを送信した時間(64bit Unix epoch)
- <dry_run> : エレベーター連携をテストしたい場合の設定キー(trueの場合、実際に利用登録は行われない)
- <requested_robot_id> : Registrationしたロボットのrobot_ID(今回は_uClosc2)
- <requested_timestamp> : ロボットからRegistrationされた時間
<result>が1(成功)の場合、<elevator_id>にはエレベーターIDが入ります。
<result>が1(成功)以外の場合、 "elevator_id" はペイロードに含まれません。
Dry run時に<result>が1(成功)の場合、"elevator_id" は含まれません。
サンプルコードの確認
サンプルコード(lci_client.py)のエレベーター利用登録(Registration)に関連する箇所を確認していきましょう。
MQTTクライアントの作成
self.mqtt_client = mqtt.Client(protocol=mqtt.MQTTv311, userdata=self)
MQTTのバージョンは、3.1.1を使用します。
コールバック関数の設定
ここでは、以下を設定しています。
- mqttブローカーに接続した際に実行する関数(
def on_connect)-
def on_connectでは、サブスクライブするトピックの設定を行います。
-
- サブスクライブしたトピックにてメッセージを受信した場合に実行する関数(
def on_message)-
def on_messageでは、受信したメッセージのペイロードをpythonの辞書型に変換します。
-
#ブローカー接続時のコールバック関数
self.mqtt_client.on_connect = on_connect
#メッセージ受信時のコールバック関数
self.mqtt_client.on_message = on_message
self.mqtt_client.enable_logger(self.logger)
def on_connect(client, userdata, flags, rc, properties=None):
userdata: LciClient = userdata
userdata.logger.info('Connected with result code {0}'.format(rc))
#サブスクライブするトピックの設定
userdata.mqtt_client.subscribe([
(userdata.topic_prefix + '/RegistrationResult/' + userdata.robot_id, 1),
(userdata.topic_prefix + '/+/RegistrationResult/' + userdata.robot_id, 1),
(userdata.topic_prefix + '/+/CallElevatorResult/' + userdata.robot_id, 1),
(userdata.topic_prefix + '/+/ElevatorStatus/' + userdata.robot_id, 1),
(userdata.topic_prefix + '/+/RobotStatusResult/' + userdata.robot_id, 1),
(userdata.topic_prefix + '/+/ReleaseResult/' + userdata.robot_id, 1)
])
# For communication with simulator
userdata.mqtt_client.subscribe([
(userdata.topic_prefix + '/+/SimulatorControlResult/' + userdata.robot_id, 1)
])
def on_message(client, userdata, msg):
try:
payload_kv = json.loads(msg.payload)
except json.JSONDecodeError as e:
userdata.logger.debug(e)
ブローカー接続
#ブローカー接続(mqtt_server:lci.octa8.link、port:8883)
self.mqtt_client.connect(str(mqtt_server), port=mqtt_port)
サブスクライブ開始
lci_client.start()
do_registration
Registration(利用登録)を行う関数になります。
この関数では、以下のことを行なっています。
- ペイロードの作成
- トピックの作成
- メッセージのパブリッシュ
- RegistrationResultのレスポンスを待つ(
def wait_response) - RegistrationResultのペイロードからelevator_idを確認
-
self.registerdをTrueにする
self.registerdをTrueにすることで、利用登録以降のシーケンスが行われるようになります。
def do_registration(self, dry_run: bool = False):
# requested_time = int(time.time() * 1000)
requested_time = int(time.time() * 1000)/1000
# dry runなし
if not dry_run:
payload = json.dumps({
'robot_id': self.robot_id,
'timestamp': requested_time})
# dry runあり
else:
payload = json.dumps({
'robot_id': self.robot_id,
'timestamp': requested_time,
'dry_run': True})
api = 'Registration'
# elevator_idが0の場合
if str(self.elevator_id) == '0':
topic = f'{self.topic_prefix}/{api}/{self.robot_id}'
# elevator_idが0以外の場合
else:
topic = f'{self.topic_prefix}/{self.elevator_id}/{api}/{self.robot_id}'
# Registrationのpublish
self._publish(topic, payload)
# RegistrationResultの受信確認
res = self.wait_response(api, requested_time, 180)
# RegistrationResultのresultが1の場合
if res != None and res.result == ResultCode.SUCCESS.value:
self.set_elevator_id(res.elevator_id)
self.registered = True
return res
wait_response
サブスクライブしたトピックのメッセージを設定した待機時間(timeout_sec)を超えるまで待つ関数になります。
def wait_response(self, orig_api: str, requested_timestamp, timeout_sec: float):
tmp = None
left_time = timeout_sec
while 0 < left_time:
while not self.response_queue.empty():
tmp = self.response_queue.get_nowait()
if tmp != None and \
tmp.orig_api == orig_api and \
tmp.requested_robot_id == self.robot_id and \
tmp.requested_timestamp == requested_timestamp:
return tmp
elif tmp.requested_timestamp < requested_timestamp:
continue
elif requested_timestamp < tmp.requested_timestamp:
return None
time.sleep(0.05)
left_time -= 0.05
return None
メイン処理
do_registration関数を実行します。
# Main routine
if __name__ == '__main__':
if len(sys.argv) < 2:
logging.error(
'Please specify yaml config file as the 1 st command line parameter.')
sys.exit(1)
lci_client = LciClient()
if not lci_client.initialize(config_file_path=sys.argv[1], cert_dir=sys.argv[2]):
logging.error('LciClient initialization error')
sys.exit(1)
lci_client.start()
time.sleep(2)
#エレベーターの利用登録
res = lci_client.do_registration()
lci_client.logger.debug(res)
time.sleep(1)
MQTTでは、QoS(Quality of Service) を設定することでメッセージの到達保証レベルを変更することができます。
サンプルコードでは、デフォルトの「0」に設定されています。
確実にメッセージを送受信するためには、QoSを
- QoS:1 最低一回はメッセージを相手に送受信
- QoS:2 メッセージを損失および重複なく送受信
のいずれかに設定しましょう。
(※LCIでは、QoSは 「1」 に設定することが推奨されています。)
具体的な設定方法としては、LciClientクラスの_publish関数内にて下記のように追記を行います。
def _publish(self, topic: str, payload: str):
self.mqtt_publish_lock.acquire()
#---追記---#
#引数にqos=1を追加
self.mqtt_client.publish(topic, payload, qos=1)
#---------#
self.mqtt_publish_lock.release()
サンプルコードの改良
サンプルコード(lci_client.py)のエレベーター利用登録では、以下の点について実装がされていません。
- resultが2, 3, 99の場合のエラー処理
- Registrationが成功するまで、30秒間隔でRegistrationを送信
- 利用登録が所定回数を超えて成功しなかった場合の処理
これらの処理を行なえるように、サンプルコードを改良していきましょう。
resultの意味と発生条件
エレベーターからロボットに送信されるRegistrationResultでは、ペイロード内のresultを確認することで、利用登録が成功したか拒否されたか、どのようなエラーの可能性があるかを知ることができます。
はじめに、resultの意味と発生条件について確認していきましょう。
| 評価順 | <result>の値 | 意味 | 発生条件 |
|---|---|---|---|
| 1 | 3 | その他エラー | ペイロードのフィールドの一部又は全部が設定されていない |
| 2 | 3 | その他エラー | トピック名のrobot_idと、ペイロードrobot_idが一致しない |
| 3 | 99 | 管制運転中 | エレベーターが管制運転中のため連携不能 |
| 4 | 3 | その他エラー | エレベーターがロボット連携モードにも関わらず、ロボットが利用登録されていない(状態のミスマッチ) |
| 5 | 2 | 拒否(失敗) | 前のRegistrationによって、ロボット連携モードへの遷移中 他のロボットが利用中 エレベーターがロボット連携モードを解除中 |
| 6 | 1 | 成功 | 要求したロボットが利用中 |
| 7 | 99 | 管制運転中 | ロボット連携モードへの遷移中に、管制運転になった ※Dryrun時は評価しない |
| 8 | 2 | 拒否(失敗) | ・ロボット連携モードへの遷移中に、Releaseで利用登録が解除された ・エレベーターにロボット連携モードを要求したが、所定の時間を経過してもロボット連携モードにならなかった(タイムアウト) ※Dryrun時は評価しない |
| 9 | 1 | 成功 | エレベーターにロボット連携モードを要求し、正しくロボット連携モードになった |
<result>が1(成功)の場合、<elevator_id>には、エレベーターIDが入ります。
<result>が1(成功)以外の場合、 "elevator_id" はペイロードに含まれません。
Dry Run時は、成功(resultが1)を返したとしても利用登録を行わず、ロボット連携モードの要求/遷移も行いません。
評価順はLCI Boxによるエラー判定の順番になります。
各resultの値に応じたロボットの動作
先ほどの表をもとに、resultの値に対してロボットがどのように動作するかを決めていきます。
今回はプロトコル仕様書を参考に以下のように定め、実装を行います。
| <result> | <result>の意味 | ロボット側の動作 |
|---|---|---|
| 1 | 成功 | 次のプロトコルに進む |
| 2 | 拒否(失敗) | 成功まで30秒間隔で繰り返し、既定回数(既定時間)を超えたら連携終了 |
| 3 | その他エラー | 異常シーケンスに遷移し、利用解除 |
| 99 | 管制運転中 | 異常シーケンスに遷移し、利用解除 |
LCIからの応答が一定時間経過してもない場合は、利用解除し連携終了とします。
今回は、Registrationの30秒間隔での実施について、初回のRegistration時から既定時間を超えても成功しなかった場合に利用解除して連携終了を行います。
フロー図
以下に、上記の表およびプロトコル仕様書を元にしたフロー図を示します。
手順としては、
- Subscribeの開始
- Registration送信(Dry Runは無し)
- RegistrationResultを受信したか確認
- 受信していない場合は、最大180秒(lci_timeout_sec)まで待機
- RegistrationResultにおけるresultの値が1の場合は、利用登録成功と判断し、次のプロトコルに進む
- result:2の場合は、成功するまで30秒間隔でregistrationを繰り返す
- Registration開始から180秒経過するまで繰り返す
- result : 3、result : 99の場合は利用解除を行う
- result:2の場合は、成功するまで30秒間隔でregistrationを繰り返す
今回は設定ファイル(server_config_simulator-1-1.yaml)に記載のあるlci_timeout_sec:180sを参考に待機時間を定めました。
ケースごとに適切な値は異なりますので、適宜値を変更してください。
実装したコード
フロー図をもとに追記した変数や関数を記載します。
追記した変数
self.timeout_secは、設定ファイルから読み取ったlci_timeout_sec(今回は180秒)になります。
class LciClient:
def __init__(self):
self.logger = logging.getLogger('lci_client')
self.mqtt_client = None
self.robot_id = None
self.timestamp = None
self.elevator_id = None
#---追記---#
self.timeout_sec = None
#---------#
def initialize(self, config_file_path: str, cert_dir: str):
yaml = ruamel.yaml.YAML()
with open(config_file_path) as file:
config = yaml.load(file.read())
#print("config :", config)
log_file = None
mqtt_server = None
log_file = config.get('lci_log_file', None)
mqtt_server = config.get('lci_mqtt_server', None)
self.bldg_id = config.get('lci_bldg_id', None)
self.bank_id = config.get('lci_bank_id', None)
self.elevator_id = config.get('lci_elevator_id', None)
floor_list_tmp = config.get('lci_floor_list', None)
#---追記---#
self.timeout_sec = config.get('lci_timeout_sec', None)
#---------#
do_registration
- RegistrarionResultのペイロード内のresultに応じた処理を行います。
- resultが3、99、またはタイムアウトした場合にはself.registeredをFalseのままにすることで、Registration以降のシーケンスが行われません。
- resultが2の場合には、30秒間隔で
self.registrationを実行します。self.timeout_secを超えても、成功しない場合にはループを終了します。
def do_registration(self, dry_run: bool = False):
max_timeout = time.time() + self.timeout_sec
while time.time() < max_timeout:
res = self.registration(dry_run)
if not res == None:
if res.result == 3 or res.result == 99 :
self.logger.error("Registration is failed!")
break
elif res.result == 2:
self.logger.error("Registration is denied.")
time.sleep(30)
elif res.result == 1:
self.registered = True
return res
else:
break
registration
def registration(self, dry_run: bool = False):
requested_time = int(time.time() * 1000)/1000
api = 'Registration'
if not dry_run:
payload = json.dumps({
'robot_id': self.robot_id,
'timestamp': requested_time})
else:
payload = json.dumps({
'robot_id': self.robot_id,
'timestamp': requested_time,
'dry_run': True})
if str(self.elevator_id) == '0':
topic = f'{self.topic_prefix}/{api}/{self.robot_id}'
else:
topic = f'{self.topic_prefix}/{self.elevator_id}/{api}/{self.robot_id}'
self._publish(topic, payload)
res = self.wait_response(api, requested_time, self.timeout_sec)
return res
動作確認
作成したコードの動作確認を行います。
今回は、以下のように追記をすることで、RegistrationResult内の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
#---追記---#
if parsed_topic.orig_api == 'registration':
#res.resultを1, 2, 3, 99のいずれかに設定
res.result = 1
else:
res.result = payload_kv.get('result', 0)
#---------#
次はresultの各値に応じて出力されたログを確認していきます。
result: 1(成功)の場合
利用登録後に、次のプロトコルに進んでいることが読み取れます。
2025-09-18 13:51:06,511 DEBUG Sending PUBLISH (d0, q1, r0, m3), 'b'/lci/simulator/1/1/Registration/_uClosc2'', ... (53 bytes)
2025-09-18 13:51:06,541 DEBUG Received PUBACK (Mid: 3)
2025-09-18 13:51:07,123 DEBUG Received PUBLISH (d0, q1, r0, m1), '/lci/simulator/1/1/RegistrationResult/_uClosc2', ... (135 bytes)
2025-09-18 13:51:07,123 DEBUG Sending PUBACK (Mid: 1)
2025-09-18 13:51:07,157 DEBUG orig_api: 'Registration', result: '1', elevator_id: '1', timestamp: '1758171067.185', requested_robot_id: '_uClosc2', requested_timestamp: '1758171066.511'
2025-09-18 13:51:08,162 DEBUG Sending PUBLISH (d0, q1, r0, m4), 'b'/lci/simulator/1/1/CallElevator/_uClosc2'', ... (156 bytes)
result: 2(失敗)の場合
resultが1になるまで30秒間隔で利用登録を行い、180秒が経過すると利用解除(Release)を行っています。
2025-09-18 13:53:47,022 DEBUG Sending PUBLISH (d0, q1, r0, m3), 'b'/lci/simulator/1/1/Registration/_uClosc2'', ... (53 bytes)
2025-09-18 13:53:47,052 DEBUG Received PUBACK (Mid: 3)
2025-09-18 13:53:47,736 DEBUG Received PUBLISH (d0, q1, r0, m1), '/lci/simulator/1/1/RegistrationResult/_uClosc2', ... (135 bytes)
2025-09-18 13:53:47,736 DEBUG Sending PUBACK (Mid: 1)
2025-09-18 13:53:47,771 ERROR Registration is denied.
・・・
2025-09-18 13:56:48,595 DEBUG None
2025-09-18 13:56:49,597 DEBUG None
2025-09-18 13:56:50,603 DEBUG None
2025-09-18 13:56:51,606 DEBUG None
2025-09-18 13:56:52,612 DEBUG None
2025-09-18 13:56:53,617 DEBUG None
2025-09-18 13:56:54,621 DEBUG None
2025-09-18 13:56:55,626 DEBUG Sending PUBLISH (d0, q1, r0, m9), 'b'/lci/simulator/1/1/Release/_uClosc2'', ... (53 bytes)
2025-09-18 13:56:55,652 DEBUG Received PUBACK (Mid: 9)
2025-09-18 13:56:55,730 DEBUG Received PUBLISH (d0, q1, r0, m1), '/lci/simulator/1/1/ReleaseResult/_uClosc2', ... (115 bytes)
2025-09-18 13:56:55,730 DEBUG Sending PUBACK (Mid: 1)
2025-09-18 13:56:55,734 DEBUG orig_api: 'Release', result: '1', elevator_id: '1', timestamp: '1758171415.787', requested_robot_id: '_uClosc2', requested_timestamp: '1758171415.625'
result: 3(その他エラー)の場合
利用登録後に利用解除(Release)を行っています。
2025-09-18 13:59:09,106 DEBUG Sending PUBLISH (d0, q1, r0, m3), 'b'/lci/simulator/1/1/Registration/_uClosc2'', ... (53 bytes)
2025-09-18 13:59:09,141 DEBUG Received PUBACK (Mid: 3)
2025-09-18 13:59:09,714 DEBUG Received PUBLISH (d0, q1, r0, m1), '/lci/simulator/1/1/RegistrationResult/_uClosc2', ... (135 bytes)
2025-09-18 13:59:09,715 DEBUG Sending PUBACK (Mid: 1)
2025-09-18 13:59:09,755 ERROR Registration is failed!
2025-09-18 13:59:09,756 DEBUG None
2025-09-18 13:59:10,761 DEBUG None
2025-09-18 13:59:11,766 DEBUG None
2025-09-18 13:59:12,769 DEBUG None
2025-09-18 13:59:13,774 DEBUG None
2025-09-18 13:59:14,779 DEBUG None
2025-09-18 13:59:15,785 DEBUG None
2025-09-18 13:59:16,786 DEBUG Sending PUBLISH (d0, q1, r0, m4), 'b'/lci/simulator/1/1/Release/_uClosc2'', ... (53 bytes)
2025-09-18 13:59:16,819 DEBUG Received PUBACK (Mid: 4)
2025-09-18 13:59:16,955 DEBUG Received PUBLISH (d0, q1, r0, m1), '/lci/simulator/1/1/ReleaseResult/_uClosc2', ... (115 bytes)
2025-09-18 13:59:16,956 DEBUG Sending PUBACK (Mid: 1)
2025-09-18 13:59:17,002 DEBUG orig_api: 'Release', result: '1', elevator_id: '1', timestamp: '1758171556.954', requested_robot_id: '_uClosc2', requested_timestamp: '1758171556.785'
result: 99(管制運転中)
利用登録後に利用解除(Release)を行っています。
2025-09-18 14:04:18,931 DEBUG Sending PUBLISH (d0, q1, r0, m3), 'b'/lci/simulator/1/1/Registration/_uClosc2'', ... (53 bytes)
2025-09-18 14:04:18,967 DEBUG Received PUBACK (Mid: 3)
2025-09-18 14:04:19,442 DEBUG Received PUBLISH (d0, q1, r0, m1), '/lci/simulator/1/1/RegistrationResult/_uClosc2', ... (135 bytes)
2025-09-18 14:04:19,443 DEBUG Sending PUBACK (Mid: 1)
2025-09-18 14:04:19,481 ERROR Registration is failed!
2025-09-18 14:04:19,481 DEBUG None
2025-09-18 14:04:20,487 DEBUG None
2025-09-18 14:04:21,492 DEBUG None
2025-09-18 14:04:22,498 DEBUG None
2025-09-18 14:04:23,503 DEBUG None
2025-09-18 14:04:24,509 DEBUG None
2025-09-18 14:04:25,512 DEBUG None
2025-09-18 14:04:26,516 DEBUG Sending PUBLISH (d0, q1, r0, m4), 'b'/lci/simulator/1/1/Release/_uClosc2'', ... (53 bytes)
2025-09-18 14:04:26,565 DEBUG Received PUBACK (Mid: 4)
2025-09-18 14:04:26,638 DEBUG Received PUBLISH (d0, q1, r0, m1), '/lci/simulator/1/1/ReleaseResult/_uClosc2', ... (114 bytes)
2025-09-18 14:04:26,639 DEBUG Sending PUBACK (Mid: 1)
2025-09-18 14:04:26,681 DEBUG orig_api: 'Release', result: '1', elevator_id: '1', timestamp: '1758171866.71', requested_robot_id: '_uClosc2', requested_timestamp: '1758171866.516'
疎通確認
実際のロボット・エレベーター(LCI)間の連携では、通信環境が悪く、MQTT通信のメッセージングが機能しないことで正常に連携ができない場合があります。
この対策として、本番のRegistration(Dry Runなし)を行う前にDry Runを使用したRegistrationによる疎通確認が挙げられます。
この疎通確認について、実装を行っていきます。
フロー図
以下に、Dry Runを使用した疎通確認のフロー図を示します。
手順としては、
- Subscribe開始
- Registration(Dry Runあり)送信
- RegistrationResultを受信
- 受信していない場合は、最大180秒(lci_timeout_sec)まで待機
- resultが1の場合は、Registration(Dry Runなし)を送信
- resultが2の場合は、成功するまで30秒間隔でregistrationを繰り返す
- 合計時間が180秒になるまで繰り返す
- result : 3、result : 99の場合は利用解除し、Registrationを送らない
基本的には、さきほどの利用登録と同じ処理になります。
実装したコード
追記した変数
self.dry_run_registeredは、dryrunによる疎通確認が成功した場合にはTrue、失敗した場合にはFalseとします。
class LciClient:
def __init__(self):
self.logger = logging.getLogger('lci_client')
self.mqtt_client = None
self.robot_id = None
self.timestamp = None
self.elevator_id = None
self.timeout_sec = None
self.registered = False
#---追記---#
self.dry_run_registered = False
#---------#
is_dry_run_registred
self.dry_run_registeredの値を返します。
def is_dry_run_registred(self):
return self.dry_run_registered
do_registration
引数のdry_runがTrueでresultが1の場合には、self.dry_run_registered = Trueにし、dry runが成功したことを共有します。
def do_registration(self, dry_run: bool = False):
max_timeout = time.time() + self.timeout_sec
while time.time() < max_timeout:
res = self.registration(dry_run)
if not res == None:
if res.result == 3 or res.result == 99 :
self.logger.error("Registration is failed!")
break
elif res.result == 2:
self.logger.error("Registration is denied.")
time.sleep(30)
elif res.result == 1:
if dry_run:
self.dry_run_registered = True
self.logger.info("DryRun OK!")
else:
self.registered = True
return res
else:
break
registration
事前にdry runが成功していない場合には、Release(利用解除)を行うように追記しました。
def registration(self, dry_run: bool = False):
#dry runが成功していたか判別
if not self.is_dry_run_registred() and not dry_run:
return
requested_time = int(time.time() * 1000)/1000
api = 'Registration'
if not dry_run:
payload = json.dumps({
'robot_id': self.robot_id,
'timestamp': requested_time})
else:
payload = json.dumps({
'robot_id': self.robot_id,
'timestamp': requested_time,
'dry_run': True})
if str(self.elevator_id) == '0':
topic = f'{self.topic_prefix}/{api}/{self.robot_id}'
else:
topic = f'{self.topic_prefix}/{self.elevator_id}/{api}/{self.robot_id}'
self._publish(topic, payload)
res = self.wait_response(api, requested_time, self.timeout_sec)
return res
メイン関数
本番の利用登録を行う前に、dry runによる利用登録を行うように変更しました。
# Main routine
if __name__ == '__main__':
if len(sys.argv) < 2:
logging.error(
'Please specify yaml config file as the 1 st command line parameter.')
sys.exit(1)
lci_client = LciClient()
if not lci_client.initialize(config_file_path=sys.argv[1], cert_dir=sys.argv[2]):
logging.error('LciClient initialization error')
sys.exit(1)
lci_client.start()
time.sleep(2)
#---追記---#
#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)
動作確認
作成したコードの動作確認を行います。
result: 1(成功)の場合
Registration(dry run)終了後、本番のRegistrationを行なっています。
2025-09-18 14:19:06,689 DEBUG Sending PUBLISH (d0, q1, r0, m3), 'b'/lci/simulator/1/1/Registration/_uClosc2'', ... (70 bytes)
2025-09-18 14:19:06,730 DEBUG Received PUBACK (Mid: 3)
2025-09-18 14:19:06,829 DEBUG Received PUBLISH (d0, q1, r0, m1), '/lci/simulator/1/1/RegistrationResult/_uClosc2', ... (151 bytes)
2025-09-18 14:19:06,829 DEBUG Sending PUBACK (Mid: 1)
2025-09-18 14:19:06,850 INFO DryRun OK!
2025-09-18 14:19:06,850 DEBUG orig_api: 'Registration', result: '1', elevator_id: '1', timestamp: '1758172746.81', requested_robot_id: '_uClosc2', requested_timestamp: '1758172746.688'
2025-09-18 14:19:07,856 DEBUG Sending PUBLISH (d0, q1, r0, m4), 'b'/lci/simulator/1/1/Registration/_uClosc2'', ... (53 bytes)
result: 2(失敗)の場合
resultが1になるまで30秒間隔でRegistration(dry run)を行い、180秒が経過するとRelease(利用解除)を行っています。
2025-09-18 14:21:25,472 DEBUG Sending PUBLISH (d0, q1, r0, m3), 'b'/lci/simulator/1/1/Registration/_uClosc2'', ... (70 bytes)
2025-09-18 14:21:25,509 DEBUG Received PUBACK (Mid: 3)
2025-09-18 14:21:25,587 DEBUG Received PUBLISH (d0, q1, r0, m1), '/lci/simulator/1/1/RegistrationResult/_uClosc2', ... (152 bytes)
2025-09-18 14:21:25,588 DEBUG Sending PUBACK (Mid: 1)
2025-09-18 14:21:25,633 ERROR Registration is denied.
・・・
2025-09-18 14:24:27,345 DEBUG None
2025-09-18 14:24:28,349 DEBUG None
2025-09-18 14:24:29,354 DEBUG None
2025-09-18 14:24:30,358 DEBUG None
2025-09-18 14:24:31,363 DEBUG None
2025-09-18 14:24:32,369 DEBUG None
2025-09-18 14:24:33,371 DEBUG None
2025-09-18 14:24:34,377 DEBUG Sending PUBLISH (d0, q1, r0, m9), 'b'/lci/simulator/1/1/Release/_uClosc2'', ... (53 bytes)
2025-09-18 14:24:34,413 DEBUG Received PUBACK (Mid: 9)
2025-09-18 14:24:34,527 DEBUG Received PUBLISH (d0, q1, r0, m1), '/lci/simulator/1/1/ReleaseResult/_uClosc2', ... (114 bytes)
2025-09-18 14:24:34,527 DEBUG Sending PUBACK (Mid: 1)
2025-09-18 14:24:34,538 DEBUG orig_api: 'Release', result: '1', elevator_id: '1', timestamp: '1758173074.47', requested_robot_id: '_uClosc2', requested_timestamp: '1758173074.376'
result: 3 (その他エラー)の場合
dry run時のresultが3のため、Release(利用解除)を行なっています。
2025-09-18 14:44:01,614 DEBUG Sending PUBLISH (d0, q1, r0, m3), 'b'/lci/simulator/1/1/Registration/_uClosc2'', ... (70 bytes)
2025-09-18 14:44:01,653 DEBUG Received PUBACK (Mid: 3)
2025-09-18 14:44:01,753 DEBUG Received PUBLISH (d0, q1, r0, m1), '/lci/simulator/1/1/RegistrationResult/_uClosc2', ... (152 bytes)
2025-09-18 14:44:01,753 DEBUG Sending PUBACK (Mid: 1)
2025-09-18 14:44:01,778 ERROR Registration is failed!
2025-09-18 14:44:01,779 DEBUG None
2025-09-18 14:44:02,784 DEBUG None
2025-09-18 14:44:03,790 DEBUG None
2025-09-18 14:44:04,793 DEBUG None
2025-09-18 14:44:05,798 DEBUG None
2025-09-18 14:44:06,804 DEBUG None
2025-09-18 14:44:07,809 DEBUG None
2025-09-18 14:44:08,813 DEBUG None
2025-09-18 14:44:09,818 DEBUG Sending PUBLISH (d0, q1, r0, m4), 'b'/lci/simulator/1/1/Release/_uClosc2'', ... (53 bytes)
2025-09-18 14:44:09,851 DEBUG Received PUBACK (Mid: 4)
2025-09-18 14:44:09,935 DEBUG Received PUBLISH (d0, q1, r0, m1), '/lci/simulator/1/1/ReleaseResult/_uClosc2', ... (115 bytes)
2025-09-18 14:44:09,936 DEBUG Sending PUBACK (Mid: 1)
2025-09-18 14:44:09,981 DEBUG orig_api: 'Release', result: '1', elevator_id: '1', timestamp: '1758174249.993', requested_robot_id: '_uClosc2', requested_timestamp: '1758174249.818'
result: 99(拒否)の場合
dry run時のresultが99のため、Release(利用解除)を行なっています。
2025-09-18 14:55:46,845 DEBUG Sending PUBLISH (d0, q1, r0, m3), 'b'/lci/simulator/1/1/Registration/_uClosc2'', ... (70 bytes)
2025-09-18 14:55:46,869 DEBUG Received PUBACK (Mid: 3)
2025-09-18 14:55:46,997 DEBUG Received PUBLISH (d0, q1, r0, m1), '/lci/simulator/1/1/RegistrationResult/_uClosc2', ... (151 bytes)
2025-09-18 14:55:46,997 DEBUG Sending PUBACK (Mid: 1)
2025-09-18 14:55:47,009 ERROR Registration is failed!
2025-09-18 14:55:47,010 DEBUG None
2025-09-18 14:55:48,016 DEBUG None
2025-09-18 14:55:49,021 DEBUG None
2025-09-18 14:55:50,024 DEBUG None
2025-09-18 14:55:51,026 DEBUG None
2025-09-18 14:55:52,031 DEBUG None
2025-09-18 14:55:53,036 DEBUG None
2025-09-18 14:55:54,041 DEBUG None
2025-09-18 14:55:55,043 DEBUG Sending PUBLISH (d0, q1, r0, m4), 'b'/lci/simulator/1/1/Release/_uClosc2'', ... (53 bytes)
2025-09-18 14:55:55,070 DEBUG Received PUBACK (Mid: 4)
2025-09-18 14:55:55,162 DEBUG Received PUBLISH (d0, q1, r0, m1), '/lci/simulator/1/1/ReleaseResult/_uClosc2', ... (115 bytes)
2025-09-18 14:55:55,162 DEBUG Sending PUBACK (Mid: 1)
2025-09-18 14:55:55,203 DEBUG orig_api: 'Release', result: '1', elevator_id: '1', timestamp: '1758174955.214', requested_robot_id: '_uClosc2', requested_timestamp: '1758174955.042'
その他
- 本記事は、LCIプロトコル仕様書(オンライン)のv2025-10-04を参考に作成・加筆しています。
- LCI関連の開発に必要な情報が記載されている開発者用ポータルは以下からアクセスできます。
- 各種資料は以下から閲覧・ダウンロードできます。
- 開発用アカウントの発行は、以下からお問い合わせください。
連載記事リスト
- (1)はじめに・自己紹介・システム概要
- (2)開発用アカウント発行・サンプルコード
- (3)MQTTのブローカー接続・MQTTについて
- (4)エレベーター連携の全体シーケンス
- (5)エレベーター利用登録(Registration)
- (6)エレベーター呼出階・行き先階指定(CallElevator)
他の記事もチェックしてみてください!
次回の記事では、通信プロトコルの「エレベーター呼出階/行き先階指定」についてサンプルコードを改良しながら詳しく解説する予定です!
ご興味があれば「いいね」やフォローをいただけると励みになります!


