web-dev-qa-db-ja.com

Python modbusライブラリ

シリアルインターフェイスでmodbusデバイスを制御する必要があります。私はmodbusの経験がありません。しかし、私の短い研究により、いくつかのmodbusライブラリが明らかになりました

長所/短所は何ですか、さらに良い選択肢がありますか?

43
P3trus

ほぼ同じ問題に直面しました-python modbus master implementationを選択するライブラリですが、私の場合はシリアル通信(modbus RTU)のため、私の観測はmodbus RTUに対してのみ有効です。

私の試験では、ドキュメントにあまり注意を払いませんでしたが、シリアルRTUマスターの例はmodbus-tkで見つけるのが最も簡単でしたが、まだwikiなどにないソースにあります。

長い話を短くする:

MinimalModbus:

  • 長所:
    • 軽量モジュール
    • パフォーマンスは、10個までのレジスタを読み取るアプリケーションで許容される場合があります
  • 短所:
    • 〜64個のレジスタを読み取ると、許容できないほど(私のアプリケーションでは)遅くなります
    • 比較的高いCPU負荷

pymodbus:

特徴的な機能:シリアルストリーム( 作成者による投稿 )に依存し、シリアルタイムアウトを動的に設定する必要があります。そうしないと、パフォーマンスが低下します(可能な限り長い応答のためにシリアルタイムアウトを調整する必要があります)

  • 長所:
    • 低CPU負荷
    • 許容性能
  • 短所:
    • タイムアウトが動的に設定されている場合でも、パフォーマンスはmodbus-tkと比較して2倍低くなります。タイムアウトを一定値のままにすると、パフォーマンスははるかに悪くなります(ただし、クエリ時間は一定です)
    • ハードウェアに敏感(シリアルバッファーからの処理ストリームへの依存の結果として)またはトランザクションに内部問題がある可能性があります:異なる読み取りまたは読み取り/書き込みが1秒あたり20回以上実行される場合、応答が混同される可能性があります。より長いタイムアウトは助けになりますが、常にシリアル回線でのpymodbus RTU実装を生産での使用に十分なほど堅牢にするわけではありません。
    • 動的シリアルポートタイムアウト設定のサポートを追加するには、追加のプログラミングが必要です。ベース同期クライアントクラスを継承し、ソケットタイムアウト変更メソッドを実装します
    • 応答の検証はmodbus-tkほど詳細ではありません。たとえば、バスの減衰の場合にのみ例外がスローされますが、modbus-tkは同じ状況で間違ったスレーブアドレスまたはCRCエラーを返し、問題の根本的な原因を特定するのに役立ちます(タイムアウトが短すぎる、バスの終了/その不足、または浮いた地面など)

modbus-tk:

特徴的な機能:データのシリアルバッファーをプローブし、応答をすばやく組み立てて返します。

  • 長所
    • 最高のパフォーマンス;動的タイムアウトを使用したpymodbusの約2倍の速度
  • 短所:
    • 約pymodbusと比較して4倍高いCPU負荷//この点を無効にして大幅に改善できます。最後のEDITセクションを参照してください
    • 大きなリクエストに対するCPU負荷の増加//は大幅に改善され、この点が無効になります。最後のEDITセクションを参照してください
    • pymodbusほどエレガントではないコード

6か月以上、最高のパフォーマンス/ CPU負荷率のためにpymodbusを使用していましたが、リクエストレートが高くなると信頼できない応答が深刻な問題になり、最終的にはより高速な組み込みシステムに移行し、最適なmodbus-tkのサポートを追加しました。

詳細に興味がある方へ

私の目標は、最短の応答時間を達成することでした。

セットアップ:

  • ボーレート:153600
    • modbusスレーブを実装するマイクロコントローラの16MHzクロックと同期して)
    • 私のrs-485バスはわずか50mです
  • FTDI FT232RコンバーターとシリアルオーバーTCPブリッジ(RFC2217モードでcom4comをブリッジとして使用)
  • uSBからシリアルへのコンバーターの場合、シリアルポート用に構成された最小のタイムアウトとバッファーサイズ(待ち時間を短縮するため)
  • auto-tx rs-485アダプター(バスが支配的な状態です)

ユースケースシナリオ:

  • 間に非同期アクセスをサポートして、1秒間に5、8または10回ポーリング
  • 10〜70レジスタの読み取り/書き込み要求

典型的な長期(週)パフォーマンス:

  • MinimalModbus:初期テスト後に削除されました
  • pymodbus:64個のレジスタを読み取るのに最大30ms。事実上最大30リクエスト/秒
    • しかし、応答は信頼できません(複数のスレッドからの同期アクセスの場合)
    • おそらくGitHubにスレッドセーフフォークがありますが、マスターの背後にあり、私はそれを試していません( https://github.com/xvart/pymodbus/network
  • modbus-tk:64レジスタを読み取るための最大16ms;小さいリクエストの場合、事実上最大70-80リクエスト/秒

基準

コード:

import time
import traceback
import serial
import modbus_tk.defines as tkCst
import modbus_tk.modbus_rtu as tkRtu

import minimalmodbus as mmRtu

from pymodbus.client.sync import ModbusSerialClient as pyRtu

slavesArr = [2]
iterSp = 100
regsSp = 10
portNbr = 21
portName = 'com22'
baudrate = 153600

timeoutSp=0.018 + regsSp*0
print "timeout: %s [s]" % timeoutSp


mmc=mmRtu.Instrument(portName, 2)  # port name, slave address
mmc.serial.baudrate=baudrate
mmc.serial.timeout=timeoutSp

tb = None
errCnt = 0
startTs = time.time()
for i in range(iterSp):
  for slaveId in slavesArr:
    mmc.address = slaveId
    try:
        mmc.read_registers(0,regsSp)
    except:
        tb = traceback.format_exc()
        errCnt += 1
stopTs = time.time()
timeDiff = stopTs  - startTs

mmc.serial.close()

print mmc.serial

print "mimalmodbus:\ttime to read %s x %s (x %s regs): %.3f [s] / %.3f [s/req]" % (len(slavesArr),iterSp, regsSp, timeDiff, timeDiff/iterSp)
if errCnt >0:
    print "   !mimalmodbus:\terrCnt: %s; last tb: %s" % (errCnt, tb)



pymc = pyRtu(method='rtu', port=portNbr, baudrate=baudrate, timeout=timeoutSp)

errCnt = 0
startTs = time.time()
for i in range(iterSp):
  for slaveId in slavesArr:
    try:
        pymc.read_holding_registers(0,regsSp,unit=slaveId)
    except:
        errCnt += 1
        tb = traceback.format_exc()
stopTs = time.time()
timeDiff = stopTs  - startTs
print "pymodbus:\ttime to read %s x %s (x %s regs): %.3f [s] / %.3f [s/req]" % (len(slavesArr),iterSp, regsSp, timeDiff, timeDiff/iterSp)
if errCnt >0:
    print "   !pymodbus:\terrCnt: %s; last tb: %s" % (errCnt, tb)
pymc.close()


tkmc = tkRtu.RtuMaster(serial.Serial(port=portNbr, baudrate=baudrate))
tkmc.set_timeout(timeoutSp)

errCnt = 0
startTs = time.time()
for i in range(iterSp):
  for slaveId in slavesArr:
    try:
        tkmc.execute(slaveId, tkCst.READ_HOLDING_REGISTERS, 0,regsSp)
    except:
        errCnt += 1
        tb = traceback.format_exc()
stopTs = time.time()
timeDiff = stopTs  - startTs
print "modbus-tk:\ttime to read %s x %s (x %s regs): %.3f [s] / %.3f [s/req]" % (len(slavesArr),iterSp, regsSp, timeDiff, timeDiff/iterSp)
if errCnt >0:
    print "   !modbus-tk:\terrCnt: %s; last tb: %s" % (errCnt, tb)
tkmc.close()

結果:

platform:
P8700 @2.53GHz
WinXP sp3 32bit
Python 2.7.1
FTDI FT232R series 1220-0
FTDI driver 2.08.26 (watch out for possible issues with 2.08.30 version on Windows)
pymodbus version 1.2.0
MinimalModbus version 0.4
modbus-tk version 0.4.2

100 x 64レジスタの読み取り:

省電力なし

timeout: 0.05 [s]
Serial<id=0xd57330, open=False>(port='com22', baudrate=153600, bytesize=8, parity='N', stopbits=1, timeout=0.05, xonxoff=False, rtscts=False, dsrdtr=False)
mimalmodbus:    time to read 1 x 100 (x 64 regs): 9.135 [s] / 0.091 [s/req]
pymodbus:       time to read 1 x 100 (x 64 regs): 6.151 [s] / 0.062 [s/req]
modbus-tk:      time to read 1 x 100 (x 64 regs): 2.280 [s] / 0.023 [s/req]

timeout: 0.03 [s]
Serial<id=0xd57330, open=False>(port='com22', baudrate=153600, bytesize=8, parity='N', stopbits=1, timeout=0.03, xonxoff=False, rtscts=False, dsrdtr=False)
mimalmodbus:    time to read 1 x 100 (x 64 regs): 7.292 [s] / 0.073 [s/req]
pymodbus:       time to read 1 x 100 (x 64 regs): 3.170 [s] / 0.032 [s/req]
modbus-tk:      time to read 1 x 100 (x 64 regs): 2.342 [s] / 0.023 [s/req]


timeout: 0.018 [s]
Serial<id=0xd57330, open=False>(port='com22', baudrate=153600, bytesize=8, parity='N', stopbits=1, timeout=0.018, xonxoff=False, rtscts=False, dsrdtr=False)
mimalmodbus:    time to read 1 x 100 (x 64 regs): 4.481 - 7.198 [s] / 0.045 - 0.072 [s/req]
pymodbus:       time to read 1 x 100 (x 64 regs): 3.045 [s] / 0.030 [s/req]
modbus-tk:      time to read 1 x 100 (x 64 regs): 2.342 [s] / 0.023 [s/req]

最大節電

timeout: 0.05 [s]
Serial<id=0xd57330, open=False>(port='com22', baudrate=153600, bytesize=8, parity='N', stopbits=1, timeout=0.05, xonxoff=False, rtscts=False, dsrdtr=False)
mimalmodbus:    time to read 1 x 100 (x 64 regs): 10.289 [s] / 0.103 [s/req]
pymodbus:       time to read 1 x 100 (x 64 regs):  6.074 [s] / 0.061 [s/req]
modbus-tk:      time to read 1 x 100 (x 64 regs):  2.358 [s] / 0.024 [s/req]

timeout: 0.03 [s]
Serial<id=0xd57330, open=False>(port='com22', baudrate=153600, bytesize=8, parity='N', stopbits=1, timeout=0.03, xonxoff=False, rtscts=False, dsrdtr=False)
mimalmodbus:    time to read 1 x 100 (x 64 regs): 8.166 [s] / 0.082 [s/req]
pymodbus:       time to read 1 x 100 (x 64 regs): 4.138 [s] / 0.041 [s/req]
modbus-tk:      time to read 1 x 100 (x 64 regs): 2.327 [s] / 0.023 [s/req]

timeout: 0.018 [s]
Serial<id=0xd57330, open=False>(port='com22', baudrate=153600, bytesize=8, parity='N', stopbits=1, timeout=0.018, xonxoff=False, rtscts=False, dsrdtr=False)
mimalmodbus:    time to read 1 x 100 (x 64 regs): 7.776 [s] / 0.078 [s/req]
pymodbus:       time to read 1 x 100 (x 64 regs): 3.169 [s] / 0.032 [s/req]
modbus-tk:      time to read 1 x 100 (x 64 regs): 2.342 [s] / 0.023 [s/req]

100 x 10レジスタの読み取り:

省電力なし

timeout: 0.05 [s]
Serial<id=0xd56350, open=False>(port='com22', baudrate=153600, bytesize=8, parity='N', stopbits=1, timeout=0.05, xonxoff=False, rtscts=False, dsrdtr=False)
mimalmodbus:    time to read 1 x 100 (x 10 regs): 6.246 [s] / 0.062 [s/req]
pymodbus:       time to read 1 x 100 (x 10 regs): 6.199 [s] / 0.062 [s/req]
modbus-tk:      time to read 1 x 100 (x 10 regs): 1.577 [s] / 0.016 [s/req]

timeout: 0.03 [s]
Serial<id=0xd56350, open=False>(port='com22', baudrate=153600, bytesize=8, parity='N', stopbits=1, timeout=0.03, xonxoff=False, rtscts=False, dsrdtr=False)
mimalmodbus:    time to read 1 x 100 (x 10 regs): 3.088 [s] / 0.031 [s/req]
pymodbus:       time to read 1 x 100 (x 10 regs): 3.143 [s] / 0.031 [s/req]
modbus-tk:      time to read 1 x 100 (x 10 regs): 1.533 [s] / 0.015 [s/req]

timeout: 0.018 [s]
Serial<id=0xd56350, open=False>(port='com22', baudrate=153600, bytesize=8, parity='N', stopbits=1, timeout=0.018, xonxoff=False, rtscts=False, dsrdtr=False)
mimalmodbus:    time to read 1 x 100 (x 10 regs): 3.066 [s] / 0.031 [s/req]
pymodbus:       time to read 1 x 100 (x 10 regs): 3.006 [s] / 0.030 [s/req]
modbus-tk:      time to read 1 x 100 (x 10 regs): 1.533 [s] / 0.015 [s/req]

最大節電

timeout: 0.05 [s]
Serial<id=0xd56350, open=False>(port='com22', baudrate=153600, bytesize=8, parity='N', stopbits=1, timeout=0.05, xonxoff=False, rtscts=False, dsrdtr=False)
mimalmodbus:    time to read 1 x 100 (x 10 regs): 6.386 [s] / 0.064 [s/req]
pymodbus:       time to read 1 x 100 (x 10 regs): 5.934 [s] / 0.059 [s/req]
modbus-tk:      time to read 1 x 100 (x 10 regs): 1.499 [s] / 0.015 [s/req]

timeout: 0.03 [s]
Serial<id=0xd56350, open=False>(port='com22', baudrate=153600, bytesize=8, parity='N', stopbits=1, timeout=0.03, xonxoff=False, rtscts=False, dsrdtr=False)
mimalmodbus:    time to read 1 x 100 (x 10 regs): 3.139 [s] / 0.031 [s/req]
pymodbus:       time to read 1 x 100 (x 10 regs): 3.170 [s] / 0.032 [s/req]
modbus-tk:      time to read 1 x 100 (x 10 regs): 1.562 [s] / 0.016 [s/req]

timeout: 0.018 [s]
Serial<id=0xd56350, open=False>(port='com22', baudrate=153600, bytesize=8, parity='N', stopbits=1, timeout=0.018, xonxoff=False, rtscts=False, dsrdtr=False)
mimalmodbus:    time to read 1 x 100 (x 10 regs): 3.123 [s] / 0.031 [s/req]
pymodbus:       time to read 1 x 100 (x 10 regs): 3.060 [s] / 0.031 [s/req]
modbus-tk:      time to read 1 x 100 (x 10 regs): 1.561 [s] / 0.016 [s/req]

実際のアプリケーション:

Modbus-rpcブリッジのロード例(〜3%はRPCサーバー部分が原因です)

  • 1秒あたり5 x 64レジスタの同期読み取りおよび同時

  • シリアルポートのタイムアウトを0.018秒に設定した非同期アクセス

    • modbus-tk

      • 10 regs:{'currentCpuUsage':20.6、 'requestsPerSec':73.2} //は改善可能;以下の編集セクションを参照してください
      • 64 regs:{'currentCpuUsage':31.2、 'requestsPerSec':41.91} //を改善できます;以下の編集セクションを参照してください
    • pymodbus:

      • 10 reg:{'currentCpuUsage':5.0、 'requestsPerSec':36.88}
      • 64 regs:{'currentCpuUsage':5.0、 'requestsPerSec':34.29}

EDIT:modbus-tkライブラリを簡単に改善して、CPU使用率を減らすことができます。元のバージョンでは、要求が送信され、T3.5スリープがマスターに渡された後、一度に1バイトの応答が組み立てられます。プロファイリングで最も証明されたのはodシリアルポートアクセスに時間がかかります。これは、シリアルバッファから予想されるデータ長を読み取ろうとすることで改善できます。 pySerial documentation =タイムアウトが設定されている場合、安全である必要があります(応答がない場合、または応答が短すぎる場合にハングアップしません)。

read(size=1)
Parameters: size – Number of bytes to read.
Returns:    Bytes read from the port.
Read size bytes from the serial port. If a timeout is set it may return less characters as   
requested. With no timeout it will block until the requested number of bytes is read. 

次の方法で「modbus_rtu.py」を変更した後:

def _recv(self, expected_length=-1):
     """Receive the response from the slave"""
     response = ""
     read_bytes = "dummy"
     iterCnt = 0
     while read_bytes:
         if iterCnt == 0:
             read_bytes = self._serial.read(expected_length)  # reduces CPU load for longer frames; serial port timeout is used anyway 
         else:
             read_bytes = self._serial.read(1)
         response += read_bytes
         if len(response) >= expected_length >= 0:
             #if the expected number of byte is received consider that the response is done
             #improve performance by avoiding end-of-response detection by timeout
             break
         iterCnt += 1

Modbus-tkの変更後、実際のアプリケーションのCPU負荷は、パフォーマンスを大幅に低下させることなく大幅に低下しました(pymodbusよりも優れています)。

Modbus-rpcブリッジの更新された負荷の例(〜3%はRPCサーバー部分が原因です)

  • 1秒あたり5 x 64レジスタの同期読み取りおよび同時

  • シリアルポートのタイムアウトを0.018秒に設定した非同期アクセス

    • modbus-tk

      • 10 regs:{'currentCpuUsage':7.8、 'requestsPerSec':66.81}
      • 64 regs:{'currentCpuUsage':8.1、 'requestsPerSec':37.61}
    • pymodbus:

      • 10 reg:{'currentCpuUsage':5.0、 'requestsPerSec':36.88}
      • 64 regs:{'currentCpuUsage':5.0、 'requestsPerSec':34.29}
109
Mr. Girgitt

Modbus を発見したばかりで、Raspberry Pi(または他の小さなSBC)のようなものに展開するのは夢です。これは、pymodbusのように10以上の依存関係をもたらさない、シンプルな単一対応パッケージです。

6
Travis Griggs

使用しているアプリケーションと達成しようとしているものに大きく依存します。

pymodbusは非常に堅牢なライブラリです。動作し、使用する多くのツールを提供します。しかし、それを使用しようとすると、少し威圧的になることがわかります。個人的に仕事をするのは難しいと感じました。 RTUとTCP/IPの両方を使用する機能を提供します。

MinimalModbusは非常にシンプルなライブラリです。必要なことを正確に行ったので、アプリケーションにこれを使用することになりました。 RTU通信のみを行い、私が知る限りそれはうまくいきます。私はそれで問題を抱えたことはありません。

私はModbus-tkを調べたことがないので、それがどこにあるのかわかりません。

最終的には、アプリケーションによって異なります。結局、pythonは私にとって最良の選択ではないことがわかりました。

4
Windsplunts