1
1

解説記事: Modbus RTUを用いたIAI RCONコントローラのPythonプログラミング

Posted at

こんにちは、今回はPythonを使用してIAI RCONコントローラをModbus RTUで制御する方法について解説します。具体的には、minimalmodbusライブラリを使用して、RCONコントローラに接続し、サーボのオン/オフ、アラームのリセット、原点復帰、ポジション番号への移動を行うプログラムを作成します。また、指定されたポジション番号への移動を繰り返し行い、qキーが押されたときにループを停止する機能も追加します。

コントロールするロボット

rcp4-main.jpg

使用するライブラリ

  • minimalmodbus: Modbus RTUプロトコルを簡単に扱うためのライブラリです。
  • serial: シリアル通信を扱うライブラリです。
  • time: 時間関連の操作を行う標準ライブラリです。
  • keyboard: キーボード入力を扱うためのライブラリです。

まずは、必要なライブラリをインストールしましょう。

pip install minimalmodbus pyserial keyboard

システムのイメージ

今回はTCP/IPではなくSerial接続のケースを解説します。
rcon.png

プログラムの全体像

以下に、完成したプログラムのコードを示します。各部分について詳細に解説していきます。

import minimalmodbus
import serial
import time
import keyboard

class RCONController:
    def __init__(self, port, slave_address):
        self.port = port
        self.slave_address = slave_address
        self.instrument = None

    def connect(self):
        minimalmodbus.BAUDRATE = 115200
        minimalmodbus.BYTESIZE = 8
        minimalmodbus.STOPBITS = 1
        minimalmodbus.TIMEOUT = 1
        minimalmodbus.PARITY = serial.PARITY_NONE
        minimalmodbus.CLOSE_PORT_AFTER_EACH_CALL = True

        try:
            self.instrument = minimalmodbus.Instrument(self.port, self.slave_address, mode=minimalmodbus.MODE_RTU)
            print(f"Connected to {self.port} with slave address {self.slave_address}")
        except IOError:
            print("Failed to connect to the instrument")

    def check_connection(self):
        if not self.instrument:
            print("Not connected to any instrument")
            return
        try:
            response = self.instrument.read_register(0, 1)  # レジスタアドレス0から1ワード読み取る
            print(f"Response: {response}")
        except IOError:
            print("Failed to communicate with the instrument")

    def reset_alarm(self):
        if not self.instrument:
            print("Not connected to any instrument")
            return
        try:
            register_address = 0x0D00  # レジスタアドレス(16進数表記)
            value_to_write = 0x0001  # 書き込む値(16進数表記)
            self.instrument.write_register(register_address, value_to_write, functioncode=6)
            print("アラームリセットコマンドを送信しました。")

            # 正常リスポンスの受信
            read_value = self.instrument.read_register(register_address)
            print(f'Read Value: {read_value}')
        except IOError:
            print("Failed to send Alarm Reset command")

    def servo_on(self):
        if not self.instrument:
            print("Not connected to any instrument")
            return
        try:
            register_address = 0x0D00  # レジスタアドレス(16進数表記)
            value_to_write = 0x1000  # データ(16進数表記)
            self.instrument.write_register(register_address, value_to_write, functioncode=6)
            print("サーボONコマンドを送信しました。")

            # 現在のサーボ状態の読み取り
            servo_status = self.instrument.read_register(register_address, functioncode=3)
            print(f'Servo Status: {servo_status}')
        except IOError:
            print("Failed to send Servo ON command")

    def home(self):
        if not self.instrument:
            print("Not connected to any instrument")
            return
        try:
            register_address = 0x0D00  # レジスタアドレス(16進数表記)
            value_to_write = 0x1010  # 書き込む値(16進数表記)
            dss1_register = 0x9005  # DSS1レジスタのアドレス(マニュアルに基づく)

            # 原点復帰指令の送信
            self.instrument.write_register(register_address, value_to_write, functioncode=6)
            print("原点復帰コマンドを送信しました。")

            # 原点復帰が完了するまで待機
            while not self.is_home_complete(dss1_register):
                time.sleep(0.1)  # 100ms待機

            print("原点復帰が完了しました。")
        except IOError:
            print("Failed to send Home command")

    def is_home_complete(self, dss1_register):
        try:
            status = self.instrument.read_register(dss1_register, functioncode=3)
            hend_bit = (status & 0x0010) >> 4  # HENDビット(ビット4)を抽出

            print(f"status, {status}")
            print(f"status & 0x0010, {status & 0x0010}")
            print(f"(status & 0x0010) >> 4, {(status & 0x0010) >> 4} ")

            if hend_bit == 1:
                print("原点復帰が完了しました。")
            else:
                print("原点復帰はまだ完了していません。")

            return hend_bit  # HENDビットが1であれば原点復帰完了
        except IOError:
            print("Failed to read DSS1 register")
            return False

    def move_to_position_number(self, position_number):
        if not self.instrument:
            print("Not connected to any instrument")
            return
        try:
            move_command_register = 0x9800  # ポジション番号指定のためのレジスタアドレス
            position_status_register = 0x9014  # 完了ポジション番号を確認するためのレジスタアドレス

            # ポジション番号指定のコマンド送信
            self.instrument.write_register(move_command_register, position_number, functioncode=6)
            print(f"ポジション番号{position_number}への移動コマンドを送信しました。")

            # 移動完了の確認
            def is_move_complete():
                status = self.instrument.read_register(position_status_register, functioncode=3)
                return (status & (1 << (position_number - 1))) != 0

            # 移動が完了するまで待機
            while not is_move_complete():
                time.sleep(0.1)  # 100ms待機

            # 完了ポジション番号を取得
            completed_position = self.instrument.read_register(position_status_register, functioncode=3)
            print(f"完了ポジション番号: {completed_position}")

        except IOError:
            print("Failed to move to the specified position number")

# メイン処理
if __name__ == "__main__":
    rcon = RCONController('COM4', 1)
    rcon.connect()
    rcon.check_connection()
    rcon.reset_alarm()
    rcon.servo_on()
    rcon.home()

    print("Press 'q' to stop the loop.")
    while True:
        rcon.move_to_position_number(2)
        print(f"ポジションNo.2への移動が完了しました。")
        rcon.move_to_position_number(1)
        print(f"ポジションNo.1への移動が完了しました。")

        if keyboard.is_pressed('q'):
            print("Stopping the loop.")
            break

プログラムの詳細解説

1. クラスの初期化

class RCONController:
    def __init__(self, port, slave_address):
        self.port = port
        self.slave_address = slave_address
        self.instrument = None

RCONControllerクラスの初期化メソッドでは、シリアルポートとスレーブアドレスを受け取り、Modbus通信を行うためのインストゥルメントを初期化します。

2. 接続メソッド

def connect(self):
    minimalmodbus.BAUDRATE = 115200
    minimalmodbus.BYTESIZE = 8
    minimalmodbus.STOPBITS = 1
    minimalmodbus.TIMEOUT = 1
    minimalmodbus.PARITY = serial.PARITY_NONE
    minimalmodbus.CLOSE_PORT_AFTER_EACH_CALL = True

    try:
        self.instrument = minimalmodbus.Instrument(self.port, self.slave_address, mode=minimalmodbus.MODE_RTU)
        print(f"Connected to {self.port} with slave address {self.slave_address}")
    except IOError:
        print("Failed to connect to the instrument")

connect

メソッドでは、Modbus通信のパラメータを設定し、指定されたシリアルポートとスレーブアドレスで接続を確立します。接続が成功すると、インストゥルメントが初期化され、成功メッセージが表示されます。

3. 接続確認メソッド

def check_connection(self):
    if not self.instrument:
        print("Not connected to any instrument")
        return
    try:
        response = self.instrument.read_register(0, 1)  # レジスタアドレス0から1ワード読み取る
        print(f"Response: {response}")
    except IOError:
        print("Failed to communicate with the instrument")

check_connectionメソッドでは、指定されたレジスタアドレスからデータを読み取り、接続が正しく確立されているかを確認します。

4. アラームリセットメソッド

def reset_alarm(self):
    if not self.instrument:
        print("Not connected to any instrument")
        return
    try:
        register_address = 0x0D00  # レジスタアドレス(16進数表記)
        value_to_write = 0x0001  # 書き込む値(16進数表記)
        self.instrument.write_register(register_address, value_to_write, functioncode=6)
        print("アラームリセットコマンドを送信しました。")

        # 正常リスポンスの受信
        read_value = self.instrument.read_register(register_address)
        print(f'Read Value: {read_value}')
    except IOError:
        print("Failed to send Alarm Reset command")

reset_alarmメソッドでは、アラームリセットコマンドを送信し、正常にリセットされたかを確認します。

ちなみに、マニュアルではこのような記載になっています。このマニュアルに準拠して、コードを作成します。

query-alarm-reset.png

5. サーボオンメソッド

def servo_on(self):
    if not self.instrument:
        print("Not connected to any instrument")
        return
    try:
        register_address = 0x0D00  # レジスタアドレス(16進数表記)
        value_to_write = 0x1000  # データ(16進数表記)
        self.instrument.write_register(register_address, value_to_write, functioncode=6)
        print("サーボONコマンドを送信しました。")

        # 現在のサーボ状態の読み取り
        servo_status = self.instrument.read_register(register_address, functioncode=3)
        print(f'Servo Status: {servo_status}')
    except IOError:
        print("Failed to send Servo ON command")

servo_onメソッドでは、サーボをオンにするコマンドを送信し、現在のサーボ状態を確認します。

6. 原点復帰メソッド

def home(self):
    if not self.instrument:
        print("Not connected to any instrument")
        return
    try:
        register_address = 0x0D00  # レジスタアドレス(16進数表記)
        value_to_write = 0x1010  # 書き込む値(16進数表記)
        dss1_register = 0x9005  # DSS1レジスタのアドレス(マニュアルに基づく)

        # 原点復帰指令の送信
        self.instrument.write_register(register_address, value_to_write, functioncode=6)
        print("原点復帰コマンドを送信しました。")

        # 原点復帰が完了するまで待機
        while not self.is_home_complete(dss1_register):
            time.sleep(0.1)  # 100ms待機

        print("原点復帰が完了しました。")
    except IOError:
        print("Failed to send Home command")

homeメソッドでは、原点復帰コマンドを送信し、原点復帰が完了するまで待機します。

7. 原点復帰完了確認メソッド

def is_home_complete(self, dss1_register):
    try:
        status = self.instrument.read_register(dss1_register, functioncode=3)
        hend_bit = (status & 0x0010) >> 4  # HENDビット(ビット4)を抽出

        print(f"status, {status}")
        print(f"status & 0x0010, {status & 0x0010}")
        print(f"(status & 0x0010) >> 4, {(status & 0x0010) >> 4} ")

        if hend_bit == 1:
            print("原点復帰が完了しました。")
        else:
            print("原点復帰はまだ完了していません。")

        return hend_bit  # HENDビットが1であれば原点復帰完了
    except IOError:
        print("Failed to read DSS1 register")
        return False

is_home_completeメソッドでは、原点復帰が完了したかどうかを確認します。DSS1レジスタのHENDビットをチェックします。

8. ポジション番号への移動メソッド

ポジション番号とは、RCONの内部に、以下のように移動条件を記録することが可能です。
この事例では、ポジション番号1とポジション番号2の間を往復する内容にします。

image.png

def move_to_position_number(self, position_number):
    if not self.instrument:
        print("Not connected to any instrument")
        return
    try:
        move_command_register = 0x9800  # ポジション番号指定のためのレジスタアドレス
        position_status_register = 0x9014  # 完了ポジション番号を確認するためのレジスタアドレス

        # ポジション番号指定のコマンド送信
        self.instrument.write_register(move_command_register, position_number, functioncode=6)
        print(f"ポジション番号{position_number}への移動コマンドを送信しました。")

        # 移動完了の確認
        def is_move_complete():
            status = self.instrument.read_register(position_status_register, functioncode=3)
            return (status & (1 << (position_number - 1))) != 0

        # 移動が完了するまで待機
        while not is_move_complete():
            time.sleep(0.1)  # 100ms待機

        # 完了ポジション番号を取得
        completed_position = self.instrument.read_register(position_status_register, functioncode=3)
        print(f"完了ポジション番号: {completed_position}")

    except IOError:
        print("Failed to move to the specified position number")

move_to_position_numberメソッドでは、指定されたポジション番号に移動するコマンドを送信し、移動が完了するまで待機します。

9. メイン処理

if __name__ == "__main__":
    rcon = RCONController('COM4', 1)
    rcon.connect()
    rcon.check_connection()
    rcon.reset_alarm()
    rcon.servo_on()
    rcon.home()

    print("Press 'q' to stop the loop.")
    while True:
        rcon.move_to_position_number(2)
        print(f"ポジションNo.2への移動が完了しました。")
        rcon.move_to_position_number(1)
        print(f"ポジションNo.1への移動が完了しました。")

        if keyboard.is_pressed('q'):
            print("Stopping the loop.")
            break

メイン処理では、RCONコントローラに接続し、アラームをリセット、サーボをオン、原点復帰を行います。その後、ポジション番号2と1への移動を繰り返し、qキーが押されたときにループを停止します。

結論

このプログラムは、IAI RCONコントローラをModbus RTUを使用して制御するための基本的な操作を実装しています。これを基に、さらに複雑な制御システムを構築することができます。Pythonとminimalmodbusライブラリを使用することで、Modbus通信を簡単に扱うことができ、工業用デバイスの制御に役立つツールとなるでしょう。

注意事項

  • 実行前に、接続するデバイスのシリアルポートやスレーブアドレスを正しく設定してください。
  • 実行中に予期しない動作が発生した場合は、すぐに停止してください。

参考資料

1
1
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
1
1