産業プロトコル Modbus RTU 対応の温湿度トランスミッタから Python で温湿度データを取得してみた

産業用プロトコル Modbus を使ってセンサーデータを取得してみました。ライブラリがあるのは便利ですね。
2021.08.12

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

最近は、Modbus や OPC-UA といった産業プロトコルを用いたデータ収集の仕組みに興味があり、Modbus 対応センサーを入手したので試してみました。

Modbus プロトコルとは?

専門分野ではないので簡単に説明すると、工場などの製造現場で使われる産業機器同士の通信規格の一つです。Modbus の他にも EtherCAT や PROFINET など多くのプロトコルが存在します。

また、Modbus には大まかに下記の3種類が存在します。

  • Modbus RTU
  • Modbus ASCII
  • Modbus/TCP

このうち今回は「Modbus RTU」を使います。

利用デバイス

今回試してみたのは、下記の「XY-MD02」という Modbus RTU 対応センサーです。安価で入手できるのでオススメです。

注意点としては、全く同じ見た目で仕様が異なる「WTR10-E」というModbus RTU 対応センサーがあります。

見た目の違いはラベルの有無くらいですが「XY-MD02」をスリットから覗くと「XY-MD02」と書かれた青い基盤が見えます。
一方で「WTR10-E」は上記の Amazon.com の商品写真にもあるように基板からして異なります。こちらは動作時に LED が点灯するようですが「XY-MD02」は光りません。

どちらも温度と湿度(相対湿度)が取れますが、結線するコネクタの位置が異なっているなど「別物」と考えておいた方がいいようです。

実は「WTR10-E」も発注済みですが、手元に届くまでしばらくかかりそうなので入手でき次第改めてご紹介したいと思います。

用意したもの一覧

全体で必要なものは下記の通りです。

品名 内容 購入リンク 備考
Modbusセンサー XY-MD02 温湿度トランスミッタ https://www.amazon.co.jp/dp/B08BJ5CPRD WTR10-Eと間違えないよう注意
RS485変換アダプタ RS485からUSBに変換するアダプタ https://www.amazon.co.jp/dp/B00GWEGZOI/
ジャンパワイヤ センサー、アダプタ、電源をつなぐワイヤ https://www.switch-science.com/catalog/620/ 「オス-オス」を使います
バッテリーホルダー リード線付きバッテリーホルダー https://www.amazon.co.jp/gp/product/B083QGC16B/ 6個タイプを使います
乾電池 電源用の乾電池 お近くの100均ショップなど 単3電池 x6 本

購入リンクを掲載しておきますが、商品によっては「WTR10-E」を買ったのに「XY-MD02」が届いた、といったことが発生する可能性があります。
(実際に経験したのですが、アマゾンの場合は商品間違いとして返品できるので改めて再注文してください)

また、電源ですがデバイスのラベルにも書かれているように「DC 5V〜30V」が必要となります。必要な容量を確保できる電池ホルダーを準備してください。

つなげてみた

実際に繋げてみた図がこちらです。デバイス側に接続先が書かれているので迷うことはないと思います。

02-sensor-all

ラベルに書かれている通り、電池のプラス/マイナスをつなげます。RS485 は変換アダプタにも「A」「B」の記載があるので同じもの同士を接続します。

03-xy-md02

ちょっと分かりづらいですが、変換アダプタの基板にも「A」「B」と書かれています。

01-rs485

これをPCに接続すれば完成です。(USBを挿すだけです)

データ取得のクエリ

次にデータの取得方法を確認しておきます。Modbus でデータを取得する場合は、取得したいデータに合わせてコマンドを組んで送信します。
コマンドの書式は次の様になります。

[ID] [Function Code] [start Address] [data size]

具体的には下記の様になります。

05-modbus-query

  • ID
    • デバイスIDです。今回は01になります。
  • Function Code
    • データの種類に応じて指定します。
    • データを参照する場合は03または04を指定します。
    • 03は参照のみで、04は参照と変更が可能です。
  • start adress
    • データを読み取る開始位置です。
  • data size
    • 読み取るデータサイズです。

これだけでは何のことかサッパリかもしれませんが、今回のデバイスは下記のドキュメントがとても参考になりました。このドキュメントを何度も読み返して理解を深める事ができました。

特に下記の表が参考になりました。
下側の表を見ると、温度と湿度が知りたい場合はRegister TypeInput Regsisterであることが分かります。(Input Registerは「Function Code 04」です)

Register TypeKeep Registerとなっているものは「Function Code 03」 のことですが、この表を見るとTemperture Correction(温度補正),Humidity Correction(湿度補正) という記載なので、このデバイスは Function Code 03 ではデータが取得できないようです。(実際に試してみましたがエラーになりました)

04-xy-md02-document

さらに詳しく知りたい方は、下記のドキュメントも参考になるかもしれません。(専門の方からすればもっと良い資料があるかもしれません。)

動作確認

ここまでできたらPCに接続して簡単に確認してみたくなりますよね。下記のようなWindows 向けの GUI ツールがあるので試してみたかったのですが、手元の Windows マシン(Windows 10)ではドライバが自動でインストールされず使えませんでした。

(それらしいドライバもいくつか見つかったのですが、今回はこのツールは使わないことにしました)

実際のコード

GUIアプリが使えなかったのでコードで確認することにします。今回は、このデバイスを Mac に接続して試してみました。Mac の環境は以下のとおりです。

 % sw_vers
ProductName:	Mac OS X
ProductVersion:	10.15.7
BuildVersion:	19H524


% python -V
Python 3.8.7

また、Python で Modbus で通信するためにpymodbusというモジュールを使います。

pip install pymodbus

実際のコードは下記です。

from pymodbus.client.sync import ModbusSerialClient as ModbusClient
import logging

FORMAT = ('%(asctime)-15s %(threadName)-15s '
          '%(levelname)-8s %(module)-15s:%(lineno)-8s %(message)s')
logging.basicConfig(format=FORMAT)
log = logging.getLogger()
log.setLevel(logging.DEBUG)

def run_sync_client():
    client = ModbusClient(baudrate=9600, port="/dev/tty.usbserial-14140", method="rtu")
    client.connect()
    log.debug("===Reading Input Registers===")
    ### read_input_registers ###
    rr = client.read_input_registers(address=1, count=2, unit=0x1)
    log.debug(rr)
 
    temperature = rr.registers[0]/10
    print(f"Temperatur:\t{temperature} ℃")
    humidity  = rr.registers[1]/10
    print(f"Humidity:\t{humidity} %")

    client.close()

if __name__ == "__main__":
    run_sync_client()

11行目のport="/dev/tty.usbserial-14140"の箇所ですが、ここは USB の RS485 変換アダプタを差す度に変わるので、お使いのマシンに応じて変更してください。
下記のコマンドを USB を挿す前後で確認することで、対象のポートを把握することができると思います。

ls -l /dev/tty.*

また、先程見たようにこのデバイスではInput Regsister、つまり「Function Code 04」でデータが取得できるようなので、コード中でもread_input_registers()を使っています。

Input Regsister()の引数ですが、先程のドキュメントの表にあったように、温度と湿度のデータを取りたい場合は「温度の開始アドレス(Regsiter Address)」である「0x0001」を指定します。 (address=1)
今回は、温度と湿度のデータの両方を取得したいので、取得したいRegister Addressは「0x0001〜0x0002」までになります。そのためcount=2としています。

温度と湿度を確認してみる

このコードを実行すると下記のようなログと、実際の温度・湿度が表示されます。

2021-08-12 05:11:01,420 MainThread      DEBUG    mysample4      :19       ===Reading Holding Registers===
2021-08-12 05:11:01,421 MainThread      DEBUG    transaction    :139      Current transaction state - IDLE
2021-08-12 05:11:01,421 MainThread      DEBUG    transaction    :144      Running transaction 1
2021-08-12 05:11:01,421 MainThread      DEBUG    transaction    :272      SEND: 0x1 0x4 0x0 0x1 0x0 0x2 0x20 0xb
2021-08-12 05:11:01,421 MainThread      DEBUG    sync           :76       New Transaction state 'SENDING'
2021-08-12 05:11:01,421 MainThread      DEBUG    transaction    :286      Changing transaction state from 'SENDING' to 'WAITING FOR REPLY'
2021-08-12 05:11:01,572 MainThread      DEBUG    transaction    :374      Changing transaction state from 'WAITING FOR REPLY' to 'PROCESSING REPLY'
2021-08-12 05:11:01,572 MainThread      DEBUG    transaction    :296      RECV: 0x1 0x4 0x4 0x1 0x1b 0x2 0x20 0x8a 0xc7
2021-08-12 05:11:01,572 MainThread      DEBUG    rtu_framer     :185      Getting Frame - 0x4 0x4 0x1 0x1b 0x2 0x20
2021-08-12 05:11:01,573 MainThread      DEBUG    factory        :266      Factory Response[ReadInputRegistersResponse: 4]
2021-08-12 05:11:01,573 MainThread      DEBUG    rtu_framer     :107      Frame advanced, resetting header!!
2021-08-12 05:11:01,573 MainThread      DEBUG    transaction    :453      Adding transaction 1
2021-08-12 05:11:01,573 MainThread      DEBUG    transaction    :464      Getting transaction 1
2021-08-12 05:11:01,573 MainThread      DEBUG    transaction    :224      Changing transaction state from 'PROCESSING REPLY' to 'TRANSACTION_COMPLETE'
2021-08-12 05:11:01,573 MainThread      DEBUG    mysample4      :31       ReadInputRegistersResponse (2)
Temperatur:	28.3 ℃
Humidity:	54.4 %

湿度はややズレがあるようですが、温度は概ね正確な値を取得できていました。(部屋の冷房設定は28℃)

最後に

簡単なスクリプトを使って Modbus でセンサーデータを取得することができました。
次はこれを Greengrass V2 で試していきたいと思います。