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の使い方「エレベーター編」#5|エレベーター利用登録(Registration)

Last updated at Posted at 2025-11-04

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

前回の記事では、「LCI」を利用して仮想のエレベータと連携する全体シーケンスを紹介しました。

第4回はこちら

今回は、全体シーケンスの中でも「利用登録(Registration)」について、実装を行っていきます。

おさらい

利用登録のシーケンスについて再確認していきましょう。

利用登録では、以下の手順でロボットとエレベーター(LCI)間でMQTT通信を行います。

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

RegistrationResultのresultが1の場合は成功RegistrationResultのresultが2、3、99のいずれかの場合は失敗とみなします。

MQTT通信はPublish(送信)/Subscribe(受信)パターンのメッセージングプロトコルです。

図1.png

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クライアントの作成

class LciClient → def initialize
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の辞書型に変換します。
class LciClient → def initialize
#ブローカー接続時のコールバック関数
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)

ブローカー接続

class LciClient → def initialize
#ブローカー接続(mqtt_server:lci.octa8.link、port:8883)
self.mqtt_client.connect(str(mqtt_server), port=mqtt_port)

サブスクライブ開始

if __name__ == '__main__'

lci_client.start()

do_registration

Registration(利用登録)を行う関数になります。

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

  • ペイロードの作成
  • トピックの作成
  • メッセージのパブリッシュ
  • RegistrationResultのレスポンスを待つ(def wait_response)
  • RegistrationResultのペイロードからelevator_idを確認
  • self.registerdTrueにする

self.registerdTrueにすることで、利用登録以降のシーケンスが行われるようになります。

class LciClient
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)を超えるまで待つ関数になります。

class LciClient
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関数内にて下記のように追記を行います。

class LciClient
  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時から既定時間を超えても成功しなかった場合に利用解除して連携終了を行います。

フロー図

以下に、上記の表およびプロトコル仕様書を元にしたフロー図を示します。

手順としては、

  1. Subscribeの開始
  2. Registration送信(Dry Runは無し)
  3. RegistrationResultを受信したか確認
    • 受信していない場合は、最大180秒(lci_timeout_sec)まで待機
  4. RegistrationResultにおけるresultの値が1の場合は、利用登録成功と判断し、次のプロトコルに進む
    • result:2の場合は、成功するまで30秒間隔でregistrationを繰り返す
      • Registration開始から180秒経過するまで繰り返す
    • result : 3、result : 99の場合は利用解除を行う

今回は設定ファイル(server_config_simulator-1-1.yaml)に記載のある​lci_timeout_sec:180sを参考に待機時間を定めました。
ケースごとに適切な値は異なりますので、適宜値を変更してください。

image.png

実装したコード

フロー図をもとに追記した変数や関数を記載します。

追記した変数

self.timeout_secは、設定ファイルから読み取ったlci_timeout_sec(今回は180秒)になります。

class LciClient
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
        #---------#
class LciClient
  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を超えても、成功しない場合にはループを終了します。
class LciClient
    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

class LciClient
  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の値を任意に置き換えることで動作確認を実施します。

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
            
        #---追記---#
        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を使用した疎通確認のフロー図を示します。

手順としては、

  1. Subscribe開始
  2. Registration(Dry Runあり)送信
  3. RegistrationResultを受信
    • 受信していない場合は、最大180秒(lci_timeout_sec)まで待機
    • resultが1の場合は、Registration(Dry Runなし)を送信
    • resultが2の場合は、成功するまで30秒間隔でregistrationを繰り返す
      • 合計時間が180秒になるまで繰り返す
    • result : 3、result : 99の場合は利用解除し、Registrationを送らない

基本的には、さきほどの利用登録と同じ処理になります。

image.png

実装したコード

追記した変数

self.dry_run_registeredは、dryrunによる疎通確認が成功した場合にはTrue、失敗した場合にはFalseとします。

class LciClient
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の値を返します。

class LciClient
   def is_dry_run_registred(self):
        return self.dry_run_registered

do_registration

引数のdry_runTrueで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
# 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'

その他

連載記事リスト

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


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


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?