1
0

EtherNet/IPでサーモカメラを遠隔操作

Posted at

EtherNet/IPとは

産業用の工作機や電子機器は、産業用プロトコルにより他の機器と通信します。
EtherNet/IPは、数ある産業用プロトコルのひとつであり、ODVA(Open DeviceNet Vendor Association, Inc.)によって管理されています。

このプロトコルは、簡単にいうとCIP(Common Industrial Protocol)をTCP/IP上で通信します。
類似のプロトコルとしては、DeviceNetやCompoNet、ControlNetなどがあります。

EtherNet/IPの特徴は、産業界で使われているCIPを、一般に広く普及しているスイッチングハブやイーサネットケーブルで構築したネットワーク上で使える点です。
TCP/IPで通信できればよいため、例えば無線LANやモバイルネットワークでの通信も可能です。

pycomm3

pycomm3は、PythonでEtherNet/IP通信を可能にするライブラリです。
同じような機能を持つライブラリは複数ありますが、とりあえずGoogle検索して最も上に登場したライブラリを使うことにしました。

以下のコマンドでインストールできます。

> pip install pycomm3

また、CondaForgeパッケージにも登録されているため、AnacondaでPython環境を構築した方は以下でもインストールできます。

> conda install -c conda-forge pycomm3

EtherNet/IP対応サーモカメラ AX8

サーモグラフィカメラ(サーモカメラ)ではFLIR社の製品が有名です。
FLIR社の製品のひとつ、AX8は固定型サーモカメラであり、ネットワーク接続して遠隔でサーモ画像を取得します。

AX8は様々なプロトコルに対応していますが、EtherNet/IPにも対応しています。
今回は、このAX8にEtherNet/IPで接続し、遠隔操作してみます。

デバイスを見つける

まず、AX8をPCと同じネットワークに接続し、電源をつけておいてください。

そのうえで以下のプログラムを実行すると、ネットワーク上に接続されたEtherNet/IP対応デバイスを列挙し、出力します。

import pycomm3
pycomm3.CIPDriver.discover()
出力例
[{'encap_protocol_version': 1,
  'ip_address': '192.168.0.180',
  'vendor': 'FLIR Systems',
  'product_type': 'Generic Device (keyable)',
  'product_code': 321,
  'revision': {'major': 2, 'minor': 40},
  'status': b'\x00\x00',
  'serial': '********',
  'product_name': 'FLIR AX8',
  'state': 255}]

serialの項目は*に置き換えています)

ちなみに、すでにIPアドレスが分かっている場合は、list_identityメソッドの引数にIPアドレスを指定して、デバイスの情報を得ることができます。

pycomm3.CIPDriver.list_identity('192.168.0.180')
出力例(一部抜粋)
{'encap_protocol_version': 1,
 'ip_address': '192.168.0.180',
...
 'state': 255}

デバイスの情報を取得する

ネットワークに接続されたAX8のIPアドレスが分かったところで、AX8の情報を取得してみます。
ここからは、AX8の仕様書とにらめっこしながらプログラミングしていきます。

EtherNet/IPでは、オブジェクトと呼ばれる、プログラミング言語ではいわゆるクラスが存在します。
クラスには番号があり、例えばIdentity Objectは1番です。
Objectは複数のインスタンスを持ちえます。
Identity Objectはどうやら1インスタンスのみのようです。

クラスは属性(Attribute)を複数持ちます。
これがデータとなります。

例えば、AX8におけるIdentity Objectの定義は以下です。

Instance Attirbute ID Name Data Type Data value Access rule
Class 1 Revision UINT 1 Get
Instance 1 1 Vendor number UINT 1161 Get
2 Device type UINT 43 Get
3 Product code number UINT 320 Get
4 Product major revision
Product minor revision
USINT
USINT
02
38
Get
5 Status WORD Always 0 Get
6 Serial number UDINT Unique 32 bit value
".version.product.serial"
Get
7 Product name SHORT STRING32 Depends on camera model. Get

これらのオブジェクトとデータのやり取りをするには、サービスを利用します。
オブジェクトごとに利用可能なサービスは異なります。
例えば、Identity Objectで利用可能なサービスは以下です。

Service code Class level Instance level Service name
05 No Yes Reset
0E Yes Yes Get_Attribute_Single

もし、Identity ObjectのProduct nameを取得した場合、以下のようにプログラムすればよいです。

with pycomm3.CIPDriver('192.168.0.180') as driver:
    res = driver.generic_message(
        service=pycomm3.Services.get_attribute_single, # <1>
        class_code=pycomm3.ClassCode.identity_object, # <2>
        instance=1, # <3>
        attribute=7, # <4>
        data_type=pycomm3.SHORT_STRING) # <5>
    print(res)
出力例
generic, 'FLIR AX8', SHORT_STRING, None

プログラムを解説します。

with pycomm3.CIPDriver('192.168.0.180') as driver:

により、指定したIPアドレスのデバイスへEtherNet/IPで接続します。
接続したのち、driverでデバイスへの操作が可能です。

res = driver.generic_message(...)

generic_messageメソッドにより、接続したデバイスに対して、サービスを実行可能になります。
ここでは、「Get_Attribute_Single」のサービスを実行するため、<1>の引数で指定しています。

続く<2>・<3>で、どのクラス、インスタンスに対してサービスを実行するかを指定します。

取得したい「Product name」は、Attirbute IDが7のため、<4>のように指定しています。

最後、「Product name」のtypeを表で確認すると「SHORT STRING32」なので、pycomm3.SHORT_STRINGと指定しています。

以上により、1つの属性の値を取得できます。
ちなみに、AX8ではIdentity Objectで利用可能なサービスのうち、データ取得に関するサービスが「Get_Attribute_Single」のみでしたが、
デバイスによっては「Get_Attribute_All」もあります。
この場合、「Get_Attribute_Single」サービスにより、一度で複数の属性をデータ取得できると思います。

温度データを取得する

AX8はサーモカメラなので、カメラ視野中の任意の箇所の温度を計測し、EtherNet/IPで出力できます。

仕様書1.3章「1.3 Assembly Object (04HEX - 8 Instances) 」のOutput Dataの中に、「Spot 1 Temperature」があるので、このデータを取得すれば良さそうです。
ただし、そのアドレスは「Byte = 36~39」とあります。
じつは、AX8ではある属性から116バイトのデータを取得してから、そのデータの36~39バイトに、「Spot 1 Temperature」が格納されています。
そのため、自分で必要なデータをパースする必要があります。

以下が、あらかじめ指定した、画像中のある特定の点の温度を取得するプログラムです。
セルシウス温度で出力するよう、絶対温度から変換してあります。
出力例は25.9度で、だいたい室温と同じになりました。

import struct
with pycomm3.CIPDriver('192.168.0.180') as driver:
    driver.open()
    res = driver.generic_message(
        service=pycomm3.Services.get_attribute_single,
        class_code=pycomm3.ClassCode.assembly,
        instance=0x64,
        attribute=3,
        )
    buffer = res[1]
    spot1_temp = struct.unpack_from('<f', buffer, 36)[0] - 273.15
    print(f"{spot1_temp:.1f}: spot1 temp")
出力例
25.9: spot1 temp

このプログラムを簡単に解説すると、

res = driver.generic_message(...)

により、116バイトのデータを取得できます。
その応答結果のデータの部分のみを変数bufferに格納し、bufferの先頭36バイト目から、32ビット浮動小数をリトルエンディアンで取得します。
取得した浮動小数は絶対温度なので、273.15を減じることで、セルシウス温度となります。

撮影する

AX8では「Image File Storage Object (69HEX- 1 Instance) 」というオブジェクトが定義されています。
仕様書の説明を読むと、「Store Image to Camera Memory」の値を1に設定すると、カメラ画像がAX8の内部メモリに画像が保存されるようです。

というわけで、撮影するプログラムは以下のプログラムとなります。
実際に実行したところ、内部に正しく記録されていました。

with pycomm3.CIPDriver('192.168.0.180') as driver:
    res = driver.generic_message(
        service=0x10, # pycomm3.Services.get_attribute_singleでもよい
        class_code=0x69,
        instance=1,
        attribute=1,
        request_data=bytearray([1]),
        data_type=pycomm3.BOOL
    )
1
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
1
0